mirror of
https://github.com/simonw/datasette.git
synced 2026-11-05 17:58:30 +01:00
Schema watcher: scan connections are untracked so close() cannot pull them from under a scan
remove_database() closes the Database, which closed every connection in _all_connections - including a catalog scan connection still in use on a worker thread: segfault (crashed an xdist worker in the full suite, reproduced with an add/remove stress loop). Database.connect(track=False) leaves the watcher's short-lived connections to the watcher, which always closes them on the thread that opened them. Regression test added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwkLo2rJWidpkDVT3HkvNv
This commit is contained in:
parent
a7359a7065
commit
c87eeceb48
3 changed files with 44 additions and 13 deletions
|
|
@ -550,3 +550,30 @@ async def test_persisted_fingerprints_skip_unchanged_files(tmp_path, monkeypatch
|
|||
assert await catalog_tables(ds2, "p1") == ["added_offline", "t"]
|
||||
assert await catalog_tables(ds2, "p0") == ["t"]
|
||||
ds2.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remove_database_during_catalog_scan(tmp_path):
|
||||
# remove_database() closes the Database while the watcher may still be
|
||||
# scanning it in a worker thread. The scan connection is not tracked by
|
||||
# the Database, so close() must not pull it out from under the scan
|
||||
# (that used to segfault).
|
||||
path = str(tmp_path / "big.db")
|
||||
conn = sqlite3.connect(path)
|
||||
for i in range(150):
|
||||
conn.execute(f"create table t{i} (a, b, c integer references t0(a))")
|
||||
conn.execute(f"create index i{i} on t{i}(a)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
ds = Datasette(settings={"schema_watch_interval_ms": 0})
|
||||
await ds.invoke_startup()
|
||||
for i in range(60):
|
||||
ds.add_database(Database(ds, path=path, is_mutable=i % 2 == 0), name=f"x{i}")
|
||||
await asyncio.sleep(0.0005 * (i % 5))
|
||||
ds.remove_database(f"x{i}")
|
||||
await asyncio.sleep(0.2)
|
||||
rows = await ds.get_internal_database().execute(
|
||||
"select count(*) from catalog_tables where database_name like 'x%'"
|
||||
)
|
||||
assert rows.first()[0] == 0
|
||||
ds.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue