benbjohnson/litestream · error
reopen ltx file at offset %d: %w
Error message
reopen ltx file at offset %d: %w
What it means
ResumableReader.Read() tried to reopen the remote LTX file stream at the current offset after a connection drop and got a non-retryable error: os.ErrNotExist (file gone, e.g. compacted away), context.Canceled/DeadlineExceeded, or the reader's context already canceled. The read fails immediately instead of entering the retry/backoff path.
Source
Thrown at internal/resumable_reader.go:86
// resumableReaderBackoff is the base delay between retry attempts, doubling
// per attempt. Zero-delay retries land every attempt inside the same provider
// throttle window (e.g. Tigris 408 load shedding), guaranteeing exhaustion.
const resumableReaderBackoff = 250 * time.Millisecond
func (r *ResumableReader) Read(p []byte) (int, error) {
if r.err != nil {
return 0, r.err
}
for {
// Reopen the stream from the current offset if the previous
// connection was closed (rc is nil after a retry).
if r.rc == nil {
rc, err := r.client.OpenLTXFile(r.ctx, r.level, r.minTXID, r.maxTXID, r.offset, 0)
if err != nil {
if errors.Is(err, os.ErrNotExist) || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || r.ctx.Err() != nil {
return 0, fmt.Errorf("reopen ltx file at offset %d: %w", r.offset, err)
}
if retryErr := r.retry(fmt.Errorf("reopen ltx file at offset %d: %w", r.offset, err)); retryErr != nil {
return 0, retryErr
}
r.logger.Debug("reopen ltx file failed, retrying",
"level", r.level, "min", r.minTXID, "max", r.maxTXID,
"offset", r.offset, "error", err, "attempt", r.retryN)
continue
}
r.rc = rc
}
n, err := r.rc.Read(p)
r.offset += int64(n)
if err == nil {
return n, nil
}View on GitHub (pinned to 4ed7a308f6)
Solutions
- If ErrNotExist: restart the restore/replication from the latest available position — the original segment no longer exists
- If context canceled/deadline: rerun with a longer deadline or without canceling; the error reflects the caller's own cancellation
- Check replica retention settings — increase retention so segments aren't removed during active reads
- Use `litestream ltx -level <level>` to see which LTX files actually exist on the replica
- Verify only one litestream instance manages the replica (compaction by a second instance may race active reads)
Defensive patterns
Strategy: try-catch
Try / catch
n, err := rr.Read(buf)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// segment compacted away — restart from current replica position
} else if errors.Is(err, context.Canceled) {
// caller canceled — expected during shutdown
}
return err
} Prevention
- Increase replica retention so segments outlive long reads
- Avoid canceling restores mid-stream; use generous deadlines
- Run a single litestream instance per replica to avoid compaction races
When it happens
Trigger: Reading a streamed LTX file when the connection dropped, then on reopen OpenLTXFile returns NotExist because the LTX file was compacted/removed on the replica in the meantime, or the caller's context was canceled (shutdown, timeout, test cancellation).
Common situations: Long-running restore interrupted by litestream compaction removing the exact segment; restore canceled by the user (Ctrl-C) mid-stream; restore exceeding a deadline; TestResumableReader_ContextCancelAbortsBackoff exercising the cancel path.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- invalid replica, checksum mismatch
- ltx file corrupted
- ltx file missing
- invalid level: must be 0-%d or "all"
- level must be between 0 and %d
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/1b002c90a61f73dc.
Report an issue: GitHub.