Skip to content

Debugger hangs when inspecting LanceDBConnection object on Python 3.13 #2053

Closed

Description

@emilradupop

Description

The VS Code Python debugger hangs when a breakpoint is hit after creating a LanceDB connection. The issue only occurs with Python 3.13.5. The same code works correctly with Python 3.11.

The program runs normally outside the debugger.

Environment
OS: Debian 13
Python: 3.13.5
VS Code Python Debugger extension: 2026.6.0
LanceDB: 0.26.1
Debugger: debugpy (latest version installed)
Steps to reproduce

Minimal example:

import lancedb

print("before connect")
db = lancedb.connect("testdb")
print("after connect")

Steps:

Open the file in VS Code.
Start debugging with F5.
Add a breakpoint on:
print("after connect")
Run the debugger.
Expected behavior

The debugger should stop at the breakpoint and allow normal debugging.

Actual behavior

The debugger stops at the breakpoint but hangs. VS Code becomes unresponsive while the breakpoint is active.

The program continues to run normally if no breakpoint is set.

Additional observations

The issue appears related to debugger inspection of the LanceDBConnection object.

The following works:

print(repr(db))

Output:

LanceDBConnection(uri='/home/msadmin/Projects/AI/prod/lancedb/testdb')

Also:

print(vars(db))

Output:

{
'storage_options': None,
'_conn': ListingDatabase(uri=/home/msadmin/Projects/AI/prod/lancedb/testdb, read_consistency_interval=None)
}

The debugger does not hang if the object is removed before the breakpoint:

db = lancedb.connect("testdb")
db = None

print("after connect")
Comparison

The same code and LanceDB version work correctly on another machine with:

Python 3.11

The issue only reproduces on:

Python 3.13.5
Suspected cause

It appears that debugpy hangs while inspecting the native-backed LanceDB object (ListingDatabase) when Python 3.13 is used.

Workaround

Removing the LanceDB connection object before hitting breakpoints avoids the problem:

db = None

or using Python 3.11 avoids the issue completely.

Activity

  1. rchiodo commented on Aug 5, 2026

    @rchiodo
    Contributor

    Thanks for the great report — we reproduced this and tracked it down. The short version: it's not a slow/looping repr, it's a thread deadlock that happens while expanding the variable in the Variables view.

    Why it happens specifically with LanceDBConnection

    When you stop at a breakpoint, debugpy suspends all threads — including background daemon threads. LanceDBConnection runs its async work on a background asyncio event loop living in a daemon thread (LanceDBBackgroundEventLoop), and several of its attributes are @property getters (e.g. read_consistency_interval) that dispatch a coroutine to that loop and block on the result (asyncio.run_coroutine_threadsafe(coro, loop).result()).

    When the debugger expands the object in the Variables view, it enumerates attributes with dir() + getattr(), which evaluates those property getters. The getter then blocks waiting on the background loop thread — but that thread is suspended by the debugger — so it waits forever. Deadlock.

    That also explains the clues in the report:

    • repr(db) is instant → __repr__ never touches those properties.
    • vars(db) works → it only reads the instance __dict__ (storage_options, _conn), with no property evaluation.
    • Setting db = None before the breakpoint avoids the hang → nothing to expand.

    The 3.13-vs-3.11 difference comes down to how the background loop thread is traced/suspended mid-coroutine (sys.monitoring on 3.12+ vs settrace on 3.11); the underlying deadlock is the same class of bug.

    We also found a smaller contributing issue: the resolver evaluated each property twice (a hasattr() followed by getattr()), doubling the cost/side-effects of expansion.

    Fix

    PR #2055 addresses both:

    1. If resolving a variable's children takes too long, debugpy now temporarily resumes the other threads until it finishes, then re-suspends them (new PYDEVD_UNBLOCK_THREADS_ON_VARIABLES_TIMEOUT, default 3s) — so the debugger recovers instead of hanging.
    2. The resolver now calls getattr() a single time instead of hasattr() + getattr().

    A regression test reproduces the exact scenario (a property that blocks on a suspended background thread).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions