mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-18 12:08:35 +00:00
fix(python): avoid async work in sync reprs (#3620)
## Summary - keep the existing synchronous `connect()` path unchanged - make `LanceDBConnection.__repr__` and `LanceTable.__repr__` side-effect-free - add a regression test that verifies sync reprs do not call the Python background loop ## Root cause The freeze is caused by debugger rendering, not by `connect()` itself: 1. debugpy stops at a breakpoint and suspends all Python threads. 2. The debugger renders the new `db_connection` local by calling `repr()`. 3. `LanceDBConnection.__repr__` reads `read_consistency_interval`. 4. That property calls `LOOP.run(...).result()`. 5. The `LanceDBBackgroundEventLoop` thread is suspended by the debugger, so `repr()` waits for a thread that cannot run. This explains why the symptom appears immediately after `connect()`: it is the first point where a connection object exists in locals and is automatically rendered. `LanceTable.__repr__` had the same problem because it also read the connection's consistency interval. This follows the same principle as #3411: `__repr__` must not trigger async work or I/O that a debugger assumes is lightweight. ## Evidence I reproduced the behavior with the real LanceDB classes and debugpy 1.8.21 using a DAP client: - latest `main` (`ff6ff099`): the debugger reported `allThreadsStopped: true`, and evaluating `repr(db_connection)` timed out - this branch (`5755a5ba`): the same evaluation returned `LanceDBConnection(uri='/tmp/lancedb-debug-repro')` immediately - setting `PYDEVD_UNBLOCK_THREADS_TIMEOUT=0` also allowed the original repr path to complete, independently confirming that it was waiting on a suspended thread The regression test creates a connection and table, replaces `LOOP.run` with a function that fails, and verifies that both reprs still work. ## Validation - `maturin develop --manifest-path python/Cargo.toml` - `python -m pytest python/python/tests/test_db.py::test_sync_repr_does_not_use_background_loop python/python/tests/test_table.py::test_consistency -q` (`4 passed`) - `ruff check .` - `ruff format --check python/python/lancedb/db.py python/python/lancedb/table.py python/python/tests/test_db.py python/python/tests/test_table.py` - `git diff --check` Refs #3611.
This commit is contained in:
@@ -688,11 +688,7 @@ class LanceDBConnection(DBConnection):
|
||||
return cls(None, _inner=inner)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val = f"{self.__class__.__name__}(uri={self._conn.uri!r}"
|
||||
if self.read_consistency_interval is not None:
|
||||
val += f", read_consistency_interval={repr(self.read_consistency_interval)}"
|
||||
val += ")"
|
||||
return val
|
||||
return f"{self.__class__.__name__}(uri={self._conn.uri!r})"
|
||||
|
||||
@override
|
||||
def serialize(self) -> str:
|
||||
|
||||
@@ -2468,13 +2468,7 @@ class LanceTable(Table):
|
||||
return LOOP.run(self._table.count_rows(filter))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val = f"{self.__class__.__name__}(name={self.name!r}"
|
||||
if self._conn.read_consistency_interval is not None:
|
||||
val += ", read_consistency_interval={!r}".format(
|
||||
self._conn.read_consistency_interval
|
||||
)
|
||||
val += f", _conn={self._conn!r})"
|
||||
return val
|
||||
return f"{self.__class__.__name__}(name={self.name!r}, _conn={self._conn!r})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
@@ -62,6 +62,21 @@ def test_basic(tmp_path):
|
||||
assert db.open_table("test").name == db["test"].name
|
||||
|
||||
|
||||
def test_sync_repr_does_not_use_background_loop(tmp_path, monkeypatch):
|
||||
from lancedb.background_loop import LOOP
|
||||
|
||||
db = lancedb.connect(tmp_path)
|
||||
table = db.create_table("test", data=[{"id": 1}])
|
||||
|
||||
def fail_run(*args, **kwargs):
|
||||
raise AssertionError("repr should not use the Python background loop")
|
||||
|
||||
monkeypatch.setattr(LOOP, "run", fail_run)
|
||||
|
||||
assert repr(db) == f"LanceDBConnection(uri={str(tmp_path)!r})"
|
||||
assert repr(table) == f"LanceTable(name='test', _conn={db!r})"
|
||||
|
||||
|
||||
def test_ingest_pd(tmp_path):
|
||||
db = lancedb.connect(tmp_path)
|
||||
|
||||
|
||||
@@ -3087,9 +3087,6 @@ def test_consistency(tmp_path, consistency_interval):
|
||||
|
||||
db2 = lancedb.connect(tmp_path, read_consistency_interval=consistency_interval)
|
||||
table2 = db2.open_table("my_table")
|
||||
if consistency_interval is not None:
|
||||
assert "read_consistency_interval=datetime.timedelta(" in repr(db2)
|
||||
assert "read_consistency_interval=datetime.timedelta(" in repr(table2)
|
||||
assert table2.version == table.version
|
||||
|
||||
table.add([{"id": 1}])
|
||||
|
||||
Reference in New Issue
Block a user