analyze: walk audit chains read-only — no migration DDL on a read op
`arborist analyze`'s per-shard audit-chain loop opened each shard with
connect(), which on first open of a file runs executescript(SCHEMA_SQL)
plus all forward migrations — DDL on a read-only walk, on 6 GB shards,
and (until 71c9848) the trigger for the fork_score_branches crash.
New store.connect_readonly(db_path): mode=ro URI, no schema bootstrap,
no migration probes. _cmd_analyze's chain-check loop uses it.
This commit is contained in:
parent
71c98487b7
commit
1dc47e01b3
2 changed files with 25 additions and 1 deletions
|
|
@ -18,6 +18,7 @@ from arborist.store import (
|
|||
append_audit,
|
||||
connect,
|
||||
connect_query,
|
||||
connect_readonly,
|
||||
stats,
|
||||
transaction,
|
||||
)
|
||||
|
|
@ -2934,7 +2935,9 @@ def _cmd_analyze(args: argparse.Namespace) -> int:
|
|||
total_events = 0
|
||||
total_breaks = 0
|
||||
for sp in discover_shards(args.global_shards_dir):
|
||||
sc = connect(sp)
|
||||
# Read-only: an audit-chain walk must not run migration DDL
|
||||
# or take a write lock on the shard (see connect_readonly).
|
||||
sc = connect_readonly(sp)
|
||||
try:
|
||||
ev, br = _check_audit_chain(sc)
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -1475,6 +1475,27 @@ def connect_query(
|
|||
return conn
|
||||
|
||||
|
||||
def connect_readonly(db_path: Path | str) -> sqlite3.Connection:
|
||||
"""Open an existing DB read-only — ``mode=ro`` URI, no schema
|
||||
bootstrap, no migration probes.
|
||||
|
||||
For CLI paths that only walk data (audit-chain checks, per-shard
|
||||
counts): a read op must neither run DDL nor take a write lock, but
|
||||
``connect()`` does both — it runs ``executescript(SCHEMA_SQL)`` plus
|
||||
the forward migrations on the first open of each file in a process.
|
||||
(That migration pass on a read-only walk is exactly what surfaced
|
||||
the ``fork_score_branches already exists`` crash.) Raises
|
||||
``sqlite3.OperationalError`` if the file is missing or unreadable;
|
||||
callers iterating a shard set should catch and skip if they expect
|
||||
stragglers.
|
||||
"""
|
||||
conn = sqlite3.connect(
|
||||
f"file:{Path(db_path)}?mode=ro", uri=True, isolation_level=None
|
||||
)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
|
||||
@contextmanager
|
||||
def transaction(conn: sqlite3.Connection) -> Iterator[sqlite3.Connection]:
|
||||
"""BEGIN IMMEDIATE / COMMIT / ROLLBACK around a block."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue