AtlatestRepositorysigil-sqlite
sigil-sqlite / tree / testtest-sqlite-wal-cooperative.sgl
1
;;; Regression test for cooperative WAL busy-retry.2
;;;3
;;; Before the fix, a contended sqlite3_step BLOCKED the single cooperative4
;;; scheduler thread for the whole busy_timeout (kiln sets 5000ms). Fiber A5
;;; could hold a WAL write lock, yield, and then be UNABLE to run to commit6
;;; because fiber B's blocking step froze the thread — a deadlock. This test7
;;; sets up exactly that shape and asserts both fibers finish quickly (well8
;;; under the busy_timeout), proving the step no longer blocks the scheduler.9
;;;10
;;; On the pre-fix runtime A commits only after ~5000ms (the busy_timeout) and11
;;; this test FAILS; on the fix A commits at ~its intended yield (~200ms) and B12
;;; retries cooperatively right after.14
(import (sigil core)15
(sigil async)16
(sigil sqlite)17
(sigil time)18
(sigil string))20
(display "Testing SQLite WAL cooperative busy-retry (deadlock regression)...\n")22
(define dbfile "/tmp/sqlite-wal-coop-test.db")23
(define A (sqlite-open dbfile))24
(sqlite-exec A "PRAGMA journal_mode=WAL")25
(sqlite-exec A "PRAGMA busy_timeout=5000")26
(sqlite-exec A "DROP TABLE IF EXISTS t")27
(sqlite-exec A "CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)")28
(define B (sqlite-open dbfile))29
(sqlite-exec B "PRAGMA busy_timeout=5000")31
(define t0 (current-milliseconds))32
(define (ms) (- (current-milliseconds) t0))33
(define a-commit-ms 0)34
(define b-ms 0)36
(with-async37
;; A: take the WAL write lock, YIELD 0.2s while holding it, then commit.38
(go (begin39
(sqlite-exec A "BEGIN IMMEDIATE")40
(sleep 0.2)41
(sqlite-exec A "INSERT INTO t (v) VALUES ('a')")42
(sqlite-exec A "COMMIT")43
(set! a-commit-ms (ms))))44
;; B: write into the contended db — must cooperatively retry, NOT freeze the45
;; scheduler thread (which would prevent A from ever committing).46
(go (begin47
(sleep 0.05)48
(sqlite-exec B "INSERT INTO t (v) VALUES ('b')")49
(set! b-ms (ms)))))51
(sqlite-close A)52
(sqlite-close B)54
;; On the fix both finish ~0.2s; on the bug A only commits after ~5s (busy_timeout).55
(define wal-cooperative-pass?56
(and (> a-commit-ms 0) (> b-ms 0)57
(< a-commit-ms 2000) (< b-ms 2000)))59
(if wal-cooperative-pass?60
(display (string-append "PASS: A committed @" (number->string a-commit-ms)61
"ms, B @" (number->string b-ms)62
"ms (cooperative retry — scheduler not frozen)\n"))63
(display (string-append "FAIL: A @" (number->string a-commit-ms)64
"ms, B @" (number->string b-ms)65
"ms (>=2000ms => blocking sqlite3_step froze the scheduler)\n")))67
;; Final top-level value drives the harness exit code: 0 on pass, error on fail.68
(if wal-cooperative-pass?69
070
(error "WAL cooperative busy-retry regression"))