benbjohnson/litestream · error

sync before disable: %w

Error message

sync before disable: %w

What it means

While disabling write support, SetWriteEnabledWithTimeout(false) tried to flush remaining dirty pages to the replica (syncToRemoteWithLock) and that sync failed. The disable is aborted — writeEnabled stays true — because silently losing dirty pages would lose committed SQLite data. The wrapped error is the underlying sync failure (conflict check, LTX upload, etc.).

Source

Thrown at vfs.go:1874

			// Check timeout if specified
			if timeout > 0 && time.Now().After(deadline) {
				close(waitDone)
				f.disabling = false
				f.cond.Broadcast() // Wake any waiting Lock() calls
				f.mu.Unlock()
				return fmt.Errorf("timeout waiting for transaction to complete (waited %v)", timeout)
			}
			f.cond.Wait() // Unlocks mu, waits for signal, relocks mu
		}
		close(waitDone) // Stop the watcher goroutine

		// Sync dirty pages if any exist
		if len(f.dirty) > 0 {
			if err := f.syncToRemoteWithLock(); err != nil {
				f.disabling = false
				f.cond.Broadcast() // Wake any waiting Lock() calls
				f.mu.Unlock()
				return fmt.Errorf("sync before disable: %w", err)
			}
		}

		// Stop sync loop and ticker if running
		if f.syncStop != nil {
			close(f.syncStop)
			f.syncStop = nil
		}
		if f.syncTicker != nil {
			f.syncTicker.Stop()
			f.syncTicker = nil
		}

		f.writeEnabled = false
		f.disabling = false
		f.cond.Broadcast() // Wake any Lock() calls waiting for disable to complete
		f.logger.Info("write support disabled")
		f.mu.Unlock()

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Read the wrapped cause; fix connectivity/credentials to the replica storage and retry SetWriteEnabled(false).
  2. Resolve TXID conflicts: if another writer took over, coordinate (lease) or reset local state with litestream reset before disabling.
  3. Keep writes enabled (the API does this automatically) and ensure the periodic sync loop succeeds first, then disable with no dirty pages.
  4. Verify replica health (litestream ltx listing) to confirm the remote is writable and consistent.
  5. Retry after the transient outage; dirty pages were preserved.

Example fix

// before
if err := file.SetWriteEnabled(false); err != nil { os.Exit(1) } // fails on S3 outage
// after
if err := file.SetWriteEnabledWithTimeout(false, 0); err != nil {
    log.Warn("write-disable failed, writes still enabled; will retry", "err", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check replica reachability before disabling writes:
itr, err := client.LTXFiles(ctx, 0, 0, false)
if err != nil { return fmt.Errorf("replica unreachable: %w", err) }
itr.Close()

Try / catch

if err := file.SetWriteEnabled(false); err != nil {
    var syncErr error
    if strings.HasPrefix(err.Error(), "sync before disable:") {
        // writes still enabled; backoff and retry disable
        time.Sleep(backoff); retry()
    }
}

Prevention

When it happens

Trigger: Calling SetWriteEnabled(false) when len(f.dirty) > 0 and the final sync fails: replica unreachable, network error, remote has newer transactions (conflict), or storage write error during WriteLTXFile.

Common situations: Shutting down writes during a network partition or S3 outage; another writer advanced the remote TXID causing a conflict in checkForConflict; expired cloud credentials at disable time; disk issues reading the write buffer during LTX creation.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/52ec7d03fd9c7779. Report an issue: GitHub.