fix problem with doc database in read-only mode
Now that the documentation-build phase of `raco setup` uses read-only
mode, contention between writing and reading sometimes (though
infrequently) triggers an SQLITE_IOERR_LOCK error. Change the SQLite
binding to expose that specific error, as well as SQLITE_IOERR_BLOCKED,
because a retry of the affected operation is appropriate.
(cherry picked from commit 6858b8df90
)
This commit is contained in:
parent
0e4eed4e29
commit
f9fec37d4a
|
@ -23,6 +23,8 @@
|
||||||
(define -db db)
|
(define -db db)
|
||||||
(define saved-tx-status #f) ;; set by with-lock, only valid while locked
|
(define saved-tx-status #f) ;; set by with-lock, only valid while locked
|
||||||
|
|
||||||
|
(sqlite3_extended_result_codes db #t)
|
||||||
|
|
||||||
;; Must finalize all stmts before closing db, but also want stmts to be
|
;; Must finalize all stmts before closing db, but also want stmts to be
|
||||||
;; independently finalizable. So db needs strong refs to stmts (but no
|
;; independently finalizable. So db needs strong refs to stmts (but no
|
||||||
;; strong refs to prepared-statement% wrappers). Actually, sqlite3 maintains
|
;; strong refs to prepared-statement% wrappers). Actually, sqlite3 maintains
|
||||||
|
@ -342,7 +344,8 @@
|
||||||
|
|
||||||
(define/private (handle* who thunk iteration)
|
(define/private (handle* who thunk iteration)
|
||||||
(call-with-values thunk
|
(call-with-values thunk
|
||||||
(lambda (s . rest)
|
(lambda (full-s . rest)
|
||||||
|
(define s (simplify-status full-s))
|
||||||
(cond [(and (= s SQLITE_BUSY) (< iteration busy-retry-limit))
|
(cond [(and (= s SQLITE_BUSY) (< iteration busy-retry-limit))
|
||||||
(sleep busy-retry-delay)
|
(sleep busy-retry-delay)
|
||||||
(handle* who thunk (add1 iteration))]
|
(handle* who thunk (add1 iteration))]
|
||||||
|
@ -352,17 +355,17 @@
|
||||||
who
|
who
|
||||||
(if (= s SQLITE_BUSY) "SQLITE_BUSY" s)
|
(if (= s SQLITE_BUSY) "SQLITE_BUSY" s)
|
||||||
iteration))
|
iteration))
|
||||||
(apply values (handle-status who s) rest)]))))
|
(apply values (handle-status who full-s) rest)]))))
|
||||||
|
|
||||||
;; Some errors can cause whole transaction to rollback;
|
;; Some errors can cause whole transaction to rollback;
|
||||||
;; (see http://www.sqlite.org/lang_transaction.html)
|
;; (see http://www.sqlite.org/lang_transaction.html)
|
||||||
;; So when those errors occur, compare current tx-status w/ saved.
|
;; So when those errors occur, compare current tx-status w/ saved.
|
||||||
;; Can't figure out how to test...
|
;; Can't figure out how to test...
|
||||||
(define/private (handle-status who s)
|
(define/private (handle-status who full-s)
|
||||||
(when (memv s maybe-rollback-status-list)
|
(when (memv (simplify-status full-s) maybe-rollback-status-list)
|
||||||
(when (and saved-tx-status -db (not (read-tx-status))) ;; was in trans, now not
|
(when (and saved-tx-status -db (not (read-tx-status))) ;; was in trans, now not
|
||||||
(set-tx-status! who 'invalid)))
|
(set-tx-status! who 'invalid)))
|
||||||
(handle-status* who s -db))
|
(handle-status* who full-s -db))
|
||||||
|
|
||||||
;; ----
|
;; ----
|
||||||
|
|
||||||
|
@ -381,7 +384,8 @@
|
||||||
;; handle-status : symbol integer -> integer
|
;; handle-status : symbol integer -> integer
|
||||||
;; Returns the status code if no error occurred, otherwise
|
;; Returns the status code if no error occurred, otherwise
|
||||||
;; raises an exception with an appropriate message.
|
;; raises an exception with an appropriate message.
|
||||||
(define (handle-status* who s db)
|
(define (handle-status* who full-s db)
|
||||||
|
(define s (simplify-status full-s))
|
||||||
(cond [(or (= s SQLITE_OK)
|
(cond [(or (= s SQLITE_OK)
|
||||||
(= s SQLITE_ROW)
|
(= s SQLITE_ROW)
|
||||||
(= s SQLITE_DONE))
|
(= s SQLITE_DONE))
|
||||||
|
@ -403,6 +407,15 @@
|
||||||
(message . ,message)
|
(message . ,message)
|
||||||
(errcode . ,s)))))]))
|
(errcode . ,s)))))]))
|
||||||
|
|
||||||
|
(define (simplify-status s)
|
||||||
|
(cond
|
||||||
|
[(or (= SQLITE_IOERR_BLOCKED s)
|
||||||
|
(= SQLITE_IOERR_LOCK s))
|
||||||
|
;; Kept in extended form, because these indicate
|
||||||
|
;; cases where retry is appropriate
|
||||||
|
s]
|
||||||
|
[else (bitwise-and s 255)]))
|
||||||
|
|
||||||
(define error-table
|
(define error-table
|
||||||
`([,SQLITE_ERROR error "unknown error"]
|
`([,SQLITE_ERROR error "unknown error"]
|
||||||
[,SQLITE_INTERNAL internal "an internal logic error in SQLite"]
|
[,SQLITE_INTERNAL internal "an internal logic error in SQLite"]
|
||||||
|
@ -414,6 +427,8 @@
|
||||||
[,SQLITE_READONLY readonly "attempt to write a readonly database"]
|
[,SQLITE_READONLY readonly "attempt to write a readonly database"]
|
||||||
[,SQLITE_INTERRUPT interrupt "operation terminated by sqlite3_interrupt()"]
|
[,SQLITE_INTERRUPT interrupt "operation terminated by sqlite3_interrupt()"]
|
||||||
[,SQLITE_IOERR ioerr "some kind of disk I/O error occurred"]
|
[,SQLITE_IOERR ioerr "some kind of disk I/O error occurred"]
|
||||||
|
[,SQLITE_IOERR_BLOCKED ioerr-blocked "some kind of disk I/O error occurred (blocked)"]
|
||||||
|
[,SQLITE_IOERR_LOCK ioerr-lock "some kind of disk I/O error occurred (lock)"]
|
||||||
[,SQLITE_CORRUPT corrupt "the database disk image is malformed"]
|
[,SQLITE_CORRUPT corrupt "the database disk image is malformed"]
|
||||||
[,SQLITE_NOTFOUND notfound "(internal only) table or record not found"]
|
[,SQLITE_NOTFOUND notfound "(internal only) table or record not found"]
|
||||||
[,SQLITE_FULL full "insertion failed because database is full"]
|
[,SQLITE_FULL full "insertion failed because database is full"]
|
||||||
|
@ -433,4 +448,5 @@
|
||||||
|
|
||||||
;; http://www.sqlite.org/lang_transaction.html
|
;; http://www.sqlite.org/lang_transaction.html
|
||||||
(define maybe-rollback-status-list
|
(define maybe-rollback-status-list
|
||||||
(list SQLITE_FULL SQLITE_IOERR SQLITE_BUSY SQLITE_NOMEM SQLITE_INTERRUPT))
|
(list SQLITE_FULL SQLITE_IOERR SQLITE_BUSY SQLITE_NOMEM SQLITE_INTERRUPT
|
||||||
|
SQLITE_IOERR_BLOCKED SQLITE_IOERR_LOCK))
|
||||||
|
|
|
@ -32,6 +32,10 @@
|
||||||
(define SQLITE_ROW 100 ) ; sqlite3_step() has another row ready */
|
(define SQLITE_ROW 100 ) ; sqlite3_step() has another row ready */
|
||||||
(define SQLITE_DONE 101 ) ; sqlite3_step() has finished executing */
|
(define SQLITE_DONE 101 ) ; sqlite3_step() has finished executing */
|
||||||
|
|
||||||
|
;; Extended error codes:
|
||||||
|
(define SQLITE_IOERR_BLOCKED (bitwise-ior SQLITE_IOERR (arithmetic-shift 11 8)))
|
||||||
|
(define SQLITE_IOERR_LOCK (bitwise-ior SQLITE_IOERR (arithmetic-shift 15 8)))
|
||||||
|
|
||||||
(define SQLITE_INTEGER 1)
|
(define SQLITE_INTEGER 1)
|
||||||
(define SQLITE_FLOAT 2)
|
(define SQLITE_FLOAT 2)
|
||||||
(define SQLITE3_TEXT 3)
|
(define SQLITE3_TEXT 3)
|
||||||
|
|
|
@ -121,6 +121,11 @@
|
||||||
(define-sqlite sqlite3_errmsg
|
(define-sqlite sqlite3_errmsg
|
||||||
(_fun _sqlite3_database -> _string))
|
(_fun _sqlite3_database -> _string))
|
||||||
|
|
||||||
|
(define-sqlite sqlite3_extended_result_codes
|
||||||
|
(_fun _sqlite3_database _bool -> _int)
|
||||||
|
;; Ok if it's unavailable:
|
||||||
|
#:fail (lambda () (lambda (db on?) 0)))
|
||||||
|
|
||||||
;; ----------------------------------------
|
;; ----------------------------------------
|
||||||
|
|
||||||
(define-sqlite sqlite3_bind_int
|
(define-sqlite sqlite3_bind_int
|
||||||
|
|
|
@ -486,7 +486,8 @@
|
||||||
(and (exn:fail:sql? v)
|
(and (exn:fail:sql? v)
|
||||||
(let ([s (exn:fail:sql-sqlstate v)])
|
(let ([s (exn:fail:sql-sqlstate v)])
|
||||||
(or (eq? s 'busy)
|
(or (eq? s 'busy)
|
||||||
(and (string? s) (regexp-match? s #rx"^40...$"))))))
|
(eq? s 'ioerr-blocked)
|
||||||
|
(eq? s 'ioerr-lock)))))
|
||||||
|
|
||||||
(define (call-with-lock-handler handler thunk)
|
(define (call-with-lock-handler handler thunk)
|
||||||
(with-handlers* ([exn:fail:retry?
|
(with-handlers* ([exn:fail:retry?
|
||||||
|
|
Loading…
Reference in New Issue
Block a user