fix(sqlite): use BEGIN IMMEDIATE without retry loop (GH#1272)
Some checks failed
CI / Check version consistency (push) Successful in 3s
CI / Check for .beads changes (push) Has been skipped
CI / Test (ubuntu-latest) (push) Failing after 8m13s
CI / Lint (push) Failing after 3m18s
CI / Test Nix Flake (push) Failing after 1m7s
Nightly Full Tests / Full Test Suite (push) Failing after 36m59s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (Windows - smoke) (push) Has been cancelled

The original PR added retry logic on top of BEGIN IMMEDIATE, but this caused
multi-minute hangs because:

1. Connection has busy_timeout=30s set via pragma
2. Each BEGIN IMMEDIATE waits up to 30s before returning SQLITE_BUSY
3. With 5 retries, worst case was 5 × 30s = 150+ seconds

The fix removes the retry loop since SQLite's busy_timeout already handles
retries internally. BEGIN IMMEDIATE still acquires the write lock early,
preventing deadlocks - we just let busy_timeout handle contention.

Root cause analysis in bd-9ldm.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ruby
2026-01-22 18:55:30 -08:00
committed by John Ogle
parent 442ad0f0e5
commit a45b441bc5
5 changed files with 32 additions and 99 deletions

View File

@@ -273,8 +273,9 @@ func (s *SQLiteStorage) CreateIssuesWithFullOptions(ctx context.Context, issues
}
defer func() { _ = conn.Close() }()
// Use retry logic with exponential backoff to handle SQLITE_BUSY under concurrent load
if err := beginImmediateWithRetry(ctx, conn, 5, 10*time.Millisecond); err != nil {
// Start IMMEDIATE transaction to acquire write lock early.
// The connection's busy_timeout pragma (30s) handles retries if locked.
if _, err := conn.ExecContext(ctx, "BEGIN IMMEDIATE"); err != nil {
return fmt.Errorf("failed to begin immediate transaction: %w", err)
}