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)