benbjohnson/litestream · error
wait for replica sync: %w
Error message
wait for replica sync: %w
What it means
lockSync tries to acquire the replica's sync semaphore non-blockingly; if another sync is in progress it registers as a waiter and blocks on semaphore acquisition. If the passed context is cancelled (or its deadline expires) while waiting, the error wraps context.Cause(ctx) with this message. It signals that the caller gave up waiting for a concurrent sync to finish, not that syncing itself failed.
Source
Thrown at replica.go:243
r.SetPos(ltx.Pos{TXID: txID})
result.synced = true
syncedFileN++
}
// Record successful sync for heartbeat monitoring.
r.db.RecordSuccessfulSync()
return result, nil
}
func (r *Replica) lockSync(ctx context.Context) error {
if r.syncSem.TryAcquire(1) {
return nil
}
r.syncWaiters.Add(1)
defer r.syncWaiters.Add(-1)
if err := r.syncSem.Acquire(ctx, 1); err != nil {
return fmt.Errorf("wait for replica sync: %w", context.Cause(ctx))
}
return nil
}
func (r *Replica) uploadLTXFile(ctx context.Context, level int, minTXID, maxTXID ltx.TXID) (err error) {
filename := r.db.LTXPath(level, minTXID, maxTXID)
f, err := os.Open(filename)
if err != nil {
return NewLTXError("open", filename, level, uint64(minTXID), uint64(maxTXID), err)
}
defer func() { _ = f.Close() }()
info, err := r.Client.WriteLTXFile(ctx, level, minTXID, maxTXID, f)
if err != nil {
return fmt.Errorf("write ltx file: %w", err)
}
r.Logger().Debug("ltx file uploaded",
"level", info.Level,View on GitHub (pinned to 4ed7a308f6)
Solutions
- Inspect context.Cause(ctx) wrapped in the message — it names the real reason (deadline exceeded, canceled).
- Use a longer-lived context (e.g. context.Background() or a longer timeout) for sync operations.
- Avoid launching concurrent syncs on the same Replica; let the store's internal sync loop drive replication.
- Ensure graceful shutdown waits for in-flight syncs before cancelling the context.
Defensive patterns
Strategy: retry
Validate before calling
// acquire with an explicit, generous timeout instead of an unbounded or tiny one ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel()
Try / catch
if err := replica.Sync(ctx); err != nil && strings.Contains(err.Error(), "wait for replica sync") {
if errors.Is(err, context.DeadlineExceeded) {
// retry later with a longer deadline
}
} Prevention
- Don't fire concurrent manual syncs; rely on the store's sync loop
- Use contexts with realistic deadlines for sync operations
- On shutdown, wait for in-flight syncs before cancelling contexts
When it happens
Trigger: Two overlapping sync attempts on the same Replica: one holds syncSem, a second calls lockSync (from syncOnce or an anonymous goroutine), and its ctx is cancelled/times out while blocked in syncSem.Acquire.
Common situations: Application shutdown cancels the context mid-sync; a caller uses a short-context deadline while a long LTX upload is in progress; repeated SyncAndWait calls from multiple goroutines.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
Related errors
- sync interval must be greater than 0
- lease not held
- disk full
- remote has newer transactions than expected
- validation failed
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/2ba23b9016f318d1.
Report an issue: GitHub.