From e0506038ba2a0ab1fda664aa6232964499a3c5b9 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Wed, 9 Sep 2015 06:13:57 -0600 Subject: [PATCH] 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. --- racket/collects/db/private/sqlite3/connection.rkt | 6 ++++-- racket/collects/db/private/sqlite3/ffi-constants.rkt | 1 + racket/collects/setup/doc-db.rkt | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/racket/collects/db/private/sqlite3/connection.rkt b/racket/collects/db/private/sqlite3/connection.rkt index 5f033ab396..59f01c7f6f 100644 --- a/racket/collects/db/private/sqlite3/connection.rkt +++ b/racket/collects/db/private/sqlite3/connection.rkt @@ -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)) diff --git a/racket/collects/db/private/sqlite3/ffi-constants.rkt b/racket/collects/db/private/sqlite3/ffi-constants.rkt index a3559d4fb0..59f0465476 100644 --- a/racket/collects/db/private/sqlite3/ffi-constants.rkt +++ b/racket/collects/db/private/sqlite3/ffi-constants.rkt @@ -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) diff --git a/racket/collects/setup/doc-db.rkt b/racket/collects/setup/doc-db.rkt index 127ba9cbf5..f5c7aafd17 100644 --- a/racket/collects/setup/doc-db.rkt +++ b/racket/collects/setup/doc-db.rkt @@ -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?