doc database: retry on SQLITE_READONLY_ROLLBACK

The SQLITE_READONLY_ROLLBACK error is supposed to mean that a crash
occurred and a hot journal exists that needs to be replayed (but
can't, because the current connection is read-only).

In practice, it seems that the error can happen even if there has been
no crash. In that case, retrying in the same was as other transient
errors allows the process to continue.

A possible reason for the spurious error: In the implementation of
SQLite, comments in hasHotJournal() mention the possibility of false
positives (ticket #3883) and how the false positive will be handled in
the playback mechanism after obtaining an exclusive lock. The check
for a read-only connection after hasHotJournal() is called, however,
happens before that lock is acquired. So, it seems like the race
condition could trigger a false SQLITE_READONLY_ROLLBACK error.
This commit is contained in:
Matthew Flatt 2015-09-09 06:13:57 -06:00
parent 707f888c83
commit e0506038ba
3 changed files with 11 additions and 3 deletions

View File

@ -453,7 +453,8 @@
(define (simplify-status s)
(cond
[(or (= SQLITE_IOERR_BLOCKED s)
(= SQLITE_IOERR_LOCK s))
(= SQLITE_IOERR_LOCK s)
(= SQLITE_READONLY_ROLLBACK s))
;; Kept in extended form, because these indicate
;; cases where retry is appropriate
s]
@ -468,6 +469,7 @@
[,SQLITE_LOCKED locked "table in the database is locked"]
[,SQLITE_NOMEM nomem "malloc() failed"]
[,SQLITE_READONLY readonly "attempt to write a readonly database"]
[,SQLITE_READONLY_ROLLBACK readonly-rollback "attempt to write a readonly database (hot journal)"]
[,SQLITE_INTERRUPT interrupt "operation terminated by sqlite3_interrupt()"]
[,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)"]
@ -495,7 +497,7 @@
SQLITE_IOERR_BLOCKED SQLITE_IOERR_LOCK))
(define include-db-file-status-list
(list SQLITE_READONLY SQLITE_PERM SQLITE_ABORT SQLITE_BUSY SQLITE_LOCKED
(list SQLITE_READONLY SQLITE_READONLY_ROLLBACK SQLITE_PERM SQLITE_ABORT SQLITE_BUSY SQLITE_LOCKED
SQLITE_IOERR SQLITE_IOERR_BLOCKED SQLITE_IOERR_LOCK SQLITE_CORRUPT
SQLITE_NOTFOUND SQLITE_FULL SQLITE_CANTOPEN SQLITE_PROTOCOL SQLITE_EMPTY
SQLITE_FORMAT SQLITE_NOTADB))

View File

@ -35,6 +35,7 @@
;; 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_READONLY_ROLLBACK (bitwise-ior SQLITE_READONLY (arithmetic-shift 3 8)))
(define SQLITE_INTEGER 1)
(define SQLITE_FLOAT 2)

View File

@ -487,7 +487,12 @@
(let ([s (exn:fail:sql-sqlstate v)])
(or (eq? s 'busy)
(eq? s 'ioerr-blocked)
(eq? s 'ioerr-lock)))))
(eq? s 'ioerr-lock)
;; The `SQLITE_READONLY_ROLLBACK` result is supposed
;; to mean that a hot journal exists due to a crash,
;; but it seems to happen even without crashes, so
;; treat it is a reason to retry:
(eq? s 'readonly-rollback)))))
(define (call-with-lock-handler handler thunk)
(with-handlers* ([exn:fail:retry?