SessionsView: isolation_level=None so the viz sees fresh REPL writes

Python sqlite3's default ("legacy" / DEFERRED) isolation mode opens an
implicit read transaction on the first SELECT and holds it until
commit() / rollback(). For a long-lived viz connection that's a read
snapshot pinned at startup — when the REPL writes a new node in
another process, the viz keeps returning yesterday's view until the
viz process restarts.

Setting isolation_level=None puts the connection in autocommit: each
SELECT runs in its own implicit transaction that ends as soon as the
cursor is consumed, so the next call picks up any WAL frames the
writer committed in between.

Surfaced by fox writing a turn in `make session` and seeing /sessions
api/find return only OLDER sessions — the new node was in nodes_fts
(confirmed via direct sqlite3 CLI query) but invisible through the
viz's cached SessionsView. Restarting the viz "fixed" it, which is the
classic shape of this pin.

Read-seam tests still pass (9/9). The cost of autocommit is one extra
syscall per SELECT to start/end the implicit transaction — negligible
for the viz's request volume.
This commit is contained in:
russell@unturf.com 2026-06-01 17:45:04 -04:00
parent 04a2b27902
commit a2a92236bc
No known key found for this signature in database

View file

@ -1105,6 +1105,16 @@ class SessionsView:
"file:{0}?mode=ro".format(path), uri=True,
check_same_thread=False,
)
# Autocommit: every SELECT runs in its own implicit
# transaction that ends as soon as the cursor is consumed.
# Without this, Python's sqlite3 default ("legacy" mode)
# opens an implicit read transaction on the first SELECT
# and holds it until commit/rollback — pinning the
# connection to a stale WAL snapshot when another process
# (the REPL writer) is committing live. The viz needs to
# see fresh writes from the user's REPL session without a
# restart.
self._conn.isolation_level = None
self._conn.row_factory = sqlite3.Row
except Exception as exc:
log.warning("arborist.read.sessions: open failed for %s: %s",