{"record":{"id":"69db4f5f6f07ed5b","repo":"benbjohnson/litestream","slug":"release-read-lock-w","errorCode":null,"errorMessage":"release read lock: %w","messagePattern":"release read lock: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db.go","lineNumber":2656,"sourceCode":"\tif db.db == nil {\n\t\treturn 0, nil\n\t}\n\n\t// Track checkpoint metrics.\n\tt := time.Now()\n\tdefer func() {\n\t\tlabels := prometheus.Labels{\"mode\": mode}\n\t\tdb.checkpointNCounterVec.With(labels).Inc()\n\t\tif err != nil {\n\t\t\tdb.checkpointErrorNCounterVec.With(labels).Inc()\n\t\t}\n\t\tdb.checkpointSecondsCounterVec.With(labels).Add(float64(time.Since(t).Seconds()))\n\t}()\n\n\t// Ensure the read lock has been removed before issuing a checkpoint.\n\t// We defer the re-acquire to ensure it occurs even on an early return.\n\tif err := db.releaseReadLock(); err != nil {\n\t\treturn 0, fmt.Errorf(\"release read lock: %w\", err)\n\t}\n\tdefer func() { _ = db.acquireReadLock(ctx) }()\n\n\t// A non-forced checkpoint is issued as \"PASSIVE\". This will only checkpoint\n\t// if there are not pending transactions. A forced checkpoint (\"RESTART\")\n\t// will wait for pending transactions to end & block new transactions before\n\t// forcing the checkpoint and restarting the WAL.\n\t//\n\t// See: https://www.sqlite.org/pragma.html#pragma_wal_checkpoint\n\trawsql := `PRAGMA wal_checkpoint(` + mode + `);`\n\n\tvar row [3]int\n\tif err := db.db.QueryRowContext(ctx, rawsql).Scan(&row[0], &row[1], &row[2]); err != nil {\n\t\treturn 0, err\n\t}\n\tdb.Logger.Debug(\"checkpoint\", \"mode\", mode, \"result\", fmt.Sprintf(\"%d,%d,%d\", row[0], row[1], row[2]))\n\n\t// Reacquire the read lock immediately after the checkpoint.","sourceCodeStart":2638,"sourceCodeEnd":2674,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L2638-L2674","documentation":"Before issuing a SQLite checkpoint, Litestream must release its long-held read lock so the checkpoint is not starved; it re-acquires the lock via defer. This error wraps a failure of db.releaseReadLock(). Until the read lock is removed, checkpoints cannot make progress, so this failure blocks all WAL truncation/restart operations.","triggerScenarios":"db.releaseReadLock() returns an error at the start of the checkpoint flow: the internal read-lock bookkeeping is inconsistent (acquire/release mismatch), the lock state was corrupted by a prior failed release, or the underlying mechanism to clear the read marker fails.","commonSituations":"A previous checkpoint attempt errored between acquire and release, leaving the lock count unbalanced; application code calling library internals directly; a crash/restart leaving stale lock state.","solutions":["Restart the Litestream process to clear in-memory read-lock state and re-establish a balanced acquire/release cycle.","Look for earlier errors in the log (e.g. a failed re-acquire) that left the lock count unbalanced.","Avoid calling db internals (acquireReadLock/releaseReadLock) from application code.","If state persists on disk, run 'litestream reset' for the affected database."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"release read lock\") {\n    // in-memory lock state likely unbalanced: restart litestream process\n    restartProcess()\n}","preventionTips":["Never call acquireReadLock/releaseReadLock from application code.","Watch for earlier lock-acquire failures that unbalance the lock count.","Restart Litestream if checkpoint failures repeat after a prior error.","Run one Litestream process per database."],"tags":["read-lock","checkpoint","locking","litestream"],"backgroundTag":"lock-acquire-failed","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}