{"record":{"id":"4e2abe5bb9ec3075","repo":"benbjohnson/litestream","slug":"timeout-waiting-for-transaction-to-complete-waite","errorCode":null,"errorMessage":"timeout waiting for transaction to complete (waited %v)","messagePattern":"timeout waiting for transaction to complete \\(waited (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"vfs.go","lineNumber":1862,"sourceCode":"\t\tdeadline := time.Now().Add(timeout)\n\t\tfor f.inTransaction {\n\t\t\t// Check context before waiting\n\t\t\tselect {\n\t\t\tcase <-f.ctx.Done():\n\t\t\t\tclose(waitDone)\n\t\t\t\tf.disabling = false\n\t\t\t\tf.cond.Broadcast() // Wake any waiting Lock() calls\n\t\t\t\tf.mu.Unlock()\n\t\t\t\treturn fmt.Errorf(\"context cancelled while waiting for transaction: %w\", f.ctx.Err())\n\t\t\tdefault:\n\t\t\t}\n\t\t\t// Check timeout if specified\n\t\t\tif timeout > 0 && time.Now().After(deadline) {\n\t\t\t\tclose(waitDone)\n\t\t\t\tf.disabling = false\n\t\t\t\tf.cond.Broadcast() // Wake any waiting Lock() calls\n\t\t\t\tf.mu.Unlock()\n\t\t\t\treturn fmt.Errorf(\"timeout waiting for transaction to complete (waited %v)\", timeout)\n\t\t\t}\n\t\t\tf.cond.Wait() // Unlocks mu, waits for signal, relocks mu\n\t\t}\n\t\tclose(waitDone) // Stop the watcher goroutine\n\n\t\t// Sync dirty pages if any exist\n\t\tif len(f.dirty) > 0 {\n\t\t\tif err := f.syncToRemoteWithLock(); err != nil {\n\t\t\t\tf.disabling = false\n\t\t\t\tf.cond.Broadcast() // Wake any waiting Lock() calls\n\t\t\t\tf.mu.Unlock()\n\t\t\t\treturn fmt.Errorf(\"sync before disable: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\t// Stop sync loop and ticker if running\n\t\tif f.syncStop != nil {\n\t\t\tclose(f.syncStop)","sourceCodeStart":1844,"sourceCodeEnd":1880,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/vfs.go#L1844-L1880","documentation":"SetWriteEnabledWithTimeout(false) was called with timeout > 0 while a SQLite transaction was active, and the transaction did not finish within the timeout. Write support remains enabled (disabling flag rolled back) and the caller receives this error after waiting the full duration.","triggerScenarios":"SetWriteEnabledWithTimeout(false, d) invoked while another goroutine holds an open transaction (f.inTransaction true) that runs longer than d — e.g. a large bulk insert, a long-running batch, or a stuck/hung transaction holding the transaction flag.","commonSituations":"Operational scripts disabling writes on a live database with long transactions and too-short timeouts; a leaked transaction (BEGIN without COMMIT/ROLLBACK) that never completes; contention where the writer is blocked and never signals f.cond.","solutions":["Increase the timeout passed to SetWriteEnabledWithTimeout to exceed your worst-case transaction duration.","Find and finish/roll back the leaked transaction (COMMIT or ROLLBACK on the holding connection).","Drain writers (close SQLite connections) before disabling writes so f.inTransaction is false.","Use timeout == 0 (SetWriteEnabled) to wait indefinitely if you must guarantee the disable succeeds.","Instrument transaction duration to detect pathological long transactions."],"exampleFix":"// before\nerr := file.SetWriteEnabledWithTimeout(false, 2*time.Second) // tx takes 10s\n// after\nerr := file.SetWriteEnabledWithTimeout(false, 60*time.Second) // exceeds max tx time","handlingStrategy":"try-catch","validationCode":"// Track transaction duration; only disable when idle:\n// if file.InTransaction() { wait or extend timeout }\n","typeGuard":null,"tryCatchPattern":"err := file.SetWriteEnabledWithTimeout(false, maxTxDuration*2)\nif err != nil && strings.Contains(err.Error(), \"timeout waiting for transaction\") {\n    // locate leaked transaction, COMMIT/ROLLBACK it, then retry\n}","preventionTips":["Size the disable timeout above your worst-case transaction duration.","Always COMMIT/ROLLBACK — audit for leaked BEGIN without end.","Drain or close writer connections before disabling writes.","Alert on long-running transactions (> p99 duration)."],"tags":["go","timeout","sqlite-vfs","transactions"],"backgroundTag":"request-timeout","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}