Commit graph

5 commits

Author SHA1 Message Date
Simon Willison
fb2b4a3ad8 Remove scratch database APIs from the connection proposal 2026-10-07 12:25:44 -07:00
Simon Willison
2bf79d0411 Torture harness: check for leaked file descriptors on macOS too
check_resources() listed /proc/self/fd, which only exists on Linux, so on
macOS every scenario crashed at the end with FileNotFoundError. macOS now
lists /dev/fd and resolves each descriptor with fcntl F_GETPATH, which,
like /proc, still reports files that have been deleted (psutil's
open_files() leaves those out). The temp directory is compared via
realpath(), since macOS reports /var/folders as /private/var/folders.

The test now fails with the harness's stderr when it exits without a
summary line, instead of an IndexError that hid the real traceback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:08:20 -07:00
Claude
06fb75cc28
Torture harness: a shared-cache lock reported as HTTP 400 is the documented P12 case
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwkLo2rJWidpkDVT3HkvNv
2026-10-04 09:36:03 +00:00
Claude
6f875274b0
Torture harness: dump all stacks on the first hang; SQLite 3.53 test fix
test_new_memory_database_is_not_scanned_at_registration filled the memory
database with VACUUM INTO, which SQLite 3.53 (the free-threaded 3.14 build)
refuses for an existing in-memory database; it now uses a plain connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwkLo2rJWidpkDVT3HkvNv
2026-10-04 09:14:44 +00:00
Claude
da523c30e5
Torture harness, and the fixes it found
tests/connection_torture.py drives one Datasette from several event loops
(threads) with the smallest pool, 1ms idle timeout and 10ms schema polling
through every connection operation, plus add/remove_database, scratch
databases, external schema changes, replaced/deleted files, cancellation,
Database.close()/Datasette.close() mid-flight and non-threaded mode. One
scenario per process, so crashes and hangs are results. It checks for hangs,
crashes, undocumented exceptions, per-database write order, lost writes,
read-your-writes, catalog freshness, and fds/threads/the instance released
after close(). tests/test_connection_torture.py runs a short fixed-seed
subset of every scenario.

Fixes for what it found:
- A cursor that left the thread owning its connection (the cursor returned
  by execute_write_many()/execute_write_script(), one returned by a write
  callback, one rejected by execute_fn(), or one held by an exception's
  frames) could be freed on the event loop thread while the connection ran
  the same SQL again; on Python < 3.12 freeing it resets the shared cached
  statement, and the other query failed with "bad parameter or other API
  misuse". Such cursors are now closed on the owning thread. Pre-existing on
  main (51 failures per 1,800 multi-loop execute_write_many calls).
- invoke_startup() is single-flight across event loops (a concurrent.futures
  future, datasette.utils.inflight) instead of _startup_sequence() holding
  an asyncio.Lock: a second loop got "bound to a different event loop" or
  hung, and two loops could run the startup hooks twice. A request made
  from a startup hook no longer re-runs or deadlocks startup.
- The index page takes one snapshot of ds.databases: a database removed,
  renamed or closed mid-request, or whose file was deleted, no longer turns
  the page into a 500. Database.size is 0 for a deleted file.

The scan-coalescing helpers move to datasette/utils/inflight.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwkLo2rJWidpkDVT3HkvNv
2026-10-04 09:07:48 +00:00