Repository navigation
Name the long-transaction monitor's abort log with database/table/txn identity - #2958
Conversation
There was a problem hiding this comment.
Code Review
This pull request improves transaction abort logging by standardizing the output format using describeCommitIdentity across database transactions (including RocksDB and LMDB). It also safely wraps native transaction ID access in a try-catch block to prevent crashes on already-closed handles. A new integration test suite is added to verify the attribution of aborted transactions. The review feedback suggests increasing the transaction expiration timeout in the new test from 20ms to 200ms to prevent flakiness in resource-constrained CI environments.
3862385 to
4b0cbc1
Compare
Why "monitor abort attribution" was red on Unit Test Node 22/24/26The production change was fine. The test expected the wrong database name, and its assertion structure hid that.
Fix (test only,
Evidence
The — Claude Opus 5.5 |
…tity() The abort log in DatabaseTransaction.ts and LMDBTransaction.ts named only the transaction's head table, which is misleading for a chain spanning several tables. Route it through describeCommitIdentity() like the stuck-commit log already does, so it carries the database name and native transaction id too, and give the LMDB copy the startedFrom suffix the RocksDB line already had. Dispatch-Task: harper-monitor-log-commit-identity Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsPwRKneHVrUNA3LdBcuME
…ad, update integration regex - Restore the request-path suffix on both abort logs (dropped when they moved to describeCommitIdentity()); startedFrom is only populated by Resource's static wrapper, so non-Resource writes would otherwise lose all request attribution on timeout. - Read the native transaction's `.id` defensively inside describeCommitIdentity(): rocksdb-js's accessor can throw on an already-closed handle, and a diagnostic log must never crash the monitor loop that calls it. - Update delete-index-atomicity-rocksdb.test.ts's Arm A regex for the new `database.table (transaction id)[, started from R.m]` identity shape; the database prefix and dropped `path:` segment broke its previous match (confirmed failing before this fix, now green). - Trim narrative/history comments in the new unit test and harden its log-line lookup to filter on the fixture's own table name, so another suite's own timed-out transaction logging the same phrase first can't be picked up instead. Dispatch-Task: harper-monitor-log-commit-identity Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsPwRKneHVrUNA3LdBcuME
Dispatch-Task: harper-monitor-log-commit-identity Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsPwRKneHVrUNA3LdBcuME
Trivial wording fix from round 3 review; only the transaction id and started-from groups in the regex are unpinned. Dispatch-Task: harper-monitor-log-commit-identity Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsPwRKneHVrUNA3LdBcuME
gemini-code-assist flagged that setTxnExpiration(20) before the two real Table.put() calls risked a premature tick firing mid-write on a loaded runner. Stay on the slow ambient expiration while the writes land, and only switch to the fast interval and force the head's own timeout past zero once the chain is fully built and the secondary link's recency is already decayed — the abort then fires deterministically on the very next tick instead of racing how long the writes took. Dispatch-Task: harper-monitor-log-commit-identity Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsPwRKneHVrUNA3LdBcuME
Dispatch-Task: harper-monitor-log-commit-identity Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsPwRKneHVrUNA3LdBcuME
Dispatch-Task: harper-monitor-log-commit-identity Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsPwRKneHVrUNA3LdBcuME
The two-table monitor-abort test pinned the logged database to `test`, but the unit config aliases data/dev/test/test2 onto one RocksDB root, and initStores names that root after whichever alias opened it first. Run alone the test saw `test`; inside test:unit:resources:core an earlier file had opened the root as `data`, so the line read `data.MonitorAbortPrimaryTable` and the match failed on every Linux leg. The poison path was never at fault. That match ran inside the transaction callback, so its AssertionError became the rejection and the regex validator reported it under "the poisoned commit must still surface as a rejection". The validator now rethrows anything other than the open-transaction error, and the attribution match runs after the rejection is checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V5mxNqhsJGMSr9SYiDUsTt Dispatch-Task: harper-2958-fix-unit-test
4b0cbc1 to
b58cba8
Compare
⊙ Problem
When the long-transaction monitor aborts a write-bearing transaction, its error line named only the visited link's bare table (
from table: <table> path: <url>). It gave no database, no native transaction id to join against the long-lived-holder sweep, and on LMDB nostarted fromresource. For a transaction spanning several databases, operators had no way to tell which store and which native handle the abort belonged to. The newer stuck-commit log already solves this withdescribeCommitIdentity().💡 Solution
Both engines' abort lines now go through the shared
describeCommitIdentity()export. The line becomes:The request-path suffix both lines already carried is kept. No write, poison or abort ordering changes: both engines still log synchronously and then call
abortDueToTimeout()on the chain head.🔧 Changes
resources/DatabaseTransaction.ts: exportsdescribeCommitIdentity(). It catches a throwing.idread, since rocksdb-js throws "Transaction has already been closed" on a reset handle. A throw while building the line would skip the abort and the rest of that monitor tick. The RocksDB abort line now uses the helper;started frommoves from a separate logger argument into the message.resources/LMDBTransaction.ts: imports the helper and uses it for its own copy of the abort line. LMDB gains thestarted fromcontext; it has no native id to print.✅ Verification
integrationTests/database/delete-index-atomicity-rocksdb.test.ts: Arm A's matcher accepts the newdb.table (transaction N)[, started from R.m]shape and still pins the table andpath: /SlowMixedHold/. The old regex fails against the new line.npm run test:integration -- integrationTests/database/delete-index-atomicity-rocksdb.test.tspassed 9/9 before the rebase. It has not been re-run on the rebased head; CI's integration workflow covers it.unitTests/resources/longLivedTransactions.test.js, "monitor abort attribution": drives a real two-database, non-source-apply RocksDB transaction through the monitor's abort branch, with the second link reachable only through the head's chain. It asserts the poisoned commit still rejects (the harper#2062 invariant), then that the line names the head'sdb.tableand native id. LMDB is skipped: LMDB has no native id to assert.test, but the unit config aliasesdata/dev/test/test2onto one RocksDB root that takes the name of whichever alias opens it first. Inside the full suite, an earlier file had opened it asdata. The head table now has its own database. The rejection check now rethrows anything other than the poison error, so a failed setup assertion reports under its own message.databefore the original test produces CI's exact failure; the fixed test passes under the same ordering.abortDueToTimeout()a no-op fails the test withMissing expected rejection: the poisoned commit must still surface as a rejection….npx mocha unitTests/resources/longLivedTransactions.test.js: 52 passing.npm run test:unit:resources:coreon the rebased head: 3414 passing, 0 failing. Before the test fix: 3413 passing, 1 failing (this test).npx prettier --checkandnpx oxlint --quieton the changed files: clean.4b0cbc134:test:unit:main(nodeAdapterMiddleware,npmUtilitiesdry_run). The same 7 failures occur onmain26a914501under the Node v26.11.1 runner.use cachetest failed on each of Node 22 and Node 24, a different test on each leg. No abort-log line appears in any of that run's server logs, and a re-run passed.— Claude Opus 5.5
🤖 Generated with Claude Code
https://claude.ai/code/session_01V5mxNqhsJGMSr9SYiDUsTt
Related PRs: #372 overlaps (wraps the same RocksDB abort line in
harperLogger.status(); whichever lands second keeps both the wrapper and the new message)Complexity: easy
Review-Coverage: authored=claude; ran=cursor-muse,gemini,codex; adjudicated=domain; declined=cursor-grok,cursor-composer,cursor-kimi; rounds=10; full=2 @ b58cba8
Review-Attention: study ~13m (critical: DatabaseTransaction.ts, LMDBTransaction.ts; decisions: abort-log-attributes-visited-link, convert-only-the-abort-line, error-line-format-change) @ b58cba8