Commitb04befbdRecorded2 Sep 2026Repositorysigil-sqlite
Measure the pin while the write is still parked
Message
The arm measured after the park ended, which passes whether or not the connection is serialised. It now takes a read AFTER the external commit and WHILE the write is parked: with the lock that read queues and returns current data; without it, it joins the pinned read transaction and returns the pre-commit count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NuSfnnNgQ81hjRygkkqWM6
Changed
test/test-sqlite-connection-pin.sgl | 34 ++++++++++++++++++++++++++++------
1 file changed, 28 insertions(+), 6 deletions(-)Diff
test/test-sqlite-connection-pin.sglmodified
@@ -44,7 +44,14 @@
44
;; write completes, everything finalizes, the pin resolves before anyone 45
;; looks, and this test passes whether or not the connection is serialised. 46
;; That is exactly how the first version of this file was vacuous.−47
(define PARK-BUDGET 0.30)+47
;; The measuring read is taken AFTER the external commit and WHILE the shared+48
;; write is still parked. That is the only moment the defect is visible:+49
;; - without the lock it runs during the park, joins the read transaction+50
;; pinned by the earlier read, and returns the PRE-COMMIT count;+51
;; - with the lock it cannot run during the park at all -- it queues behind+52
;; the write and returns current data, which is the fix working.+53
;; Measuring after the park ends passes either way; that is how the first+54
;; version of this file was vacuous. 55
(define failures '()) 56
(define (fail! msg) (set! failures (cons msg failures))) 57
@@ -76,12 +83,12 @@
83
;; 84
;; `read-during?` is the ONLY difference between them. 85
(define (run-pin-arm name path read-during?)−79
(sqlite-set-busy-budget! PARK-BUDGET) 86
(let ((C (make-db path)) 87
(P #f) 88
(committed #f) (reheld #f) (released #f) 89
(started 0) (finished 0)−84
(early 'not-taken)+90
(early (quote not-taken))+91
(during (quote not-taken)) 92
(shared 'unset) (fresh 'unset)) 93
(set! P (sqlite-open path)) 94
(with-async@@ -112,7 +119,12 @@
119
(go (when read-during? 120
(sleep 0.15) 121
(set! early (shared-count C))))−115
;; The measurement, once the external commit is durable.+122
;; THE MEASUREMENT: after the external commit, while the write is parked.+123
(go (begin+124
(let loop ((n 0))+125
(when (and (not reheld) (< n 4000)) (sleep 0.005) (loop (+ n 1))))+126
(set! during (shared-count C))))+127
;; And a final reading once everything has drained. 128
(go (begin 129
(let loop ((n 0)) 130
(when (and (not reheld) (< n 2000)) (sleep 0.005) (loop (+ n 1))))@@ -128,7 +140,9 @@
140
(display (string-append " " name 141
": shared=" (number->string shared) 142
" fresh=" (number->string fresh)−131
" early-read=" (if (eq? early 'not-taken) "not-taken"+143
" during-park-read=" (if (eq? during (quote not-taken)) "not-taken"+144
(number->string during))+145
" early-read=" (if (eq? early (quote not-taken)) "not-taken" 146
(number->string early)) 147
"\n")) 148
;; SETUP ASSERTIONS FIRST. Without these an "agree" could be two equal@@ -142,7 +156,15 @@
156
(when (and read-during? (eq? early 'not-taken)) 157
(fail! (string-append name ": SETUP -- the during-park read never happened, " 158
"so this arm did not test what it claims")))−145
;; THE ASSERTION.+159
;; THE ASSERTION: the read taken after the commit must see the commit.+160
(when (eq? during (quote not-taken))+161
(fail! (string-append name ": SETUP -- the during-park read never ran")))+162
(when (and (not (eq? during (quote not-taken))) (not (= during (+ BASE SEED))))+163
(fail! (string-append name ": THE CONNECTION IS PINNED. A read taken after the "+164
"external commit returned " (number->string during)+165
" rows, not " (number->string (+ BASE SEED))+166
". It ran while a write on the same connection was parked "+167
"and joined a read transaction that could not be released."))) 168
(when (not (equal? shared fresh)) 169
(fail! (string-append name ": THE CONNECTION IS PINNED. It reports " 170
(number->string shared)