{"record":{"id":"31fae8988acf0743","repo":"benbjohnson/litestream","slug":"close-level-d-ltx-iterator-w","errorCode":null,"errorMessage":"close level %d ltx iterator: %w","messagePattern":"close level (.+?) ltx iterator: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"replica.go","lineNumber":1014,"sourceCode":"\t\treturn fmt.Errorf(\"close decoder: %w\", err)\n\t}\n\n\treturn f.Sync()\n}\n\n// fillFollowGap attempts to bridge a gap in level 0 files by searching\n// higher compaction levels for a file that covers the missing TXID range.\nfunc (r *Replica) fillFollowGap(ctx context.Context, f *os.File, afterTXID ltx.TXID, gapMinTXID ltx.TXID, pageSize uint32) (ltx.TXID, error) {\n\tcurrentTXID := afterTXID\n\n\tfor level := 1; level < SnapshotLevel; level++ {\n\t\titr, err := r.Client.LTXFiles(ctx, level, 0, false)\n\t\tif err != nil {\n\t\t\treturn currentTXID, fmt.Errorf(\"list level %d ltx files: %w\", level, err)\n\t\t}\n\t\tcloseLevel := func(retErr error) (ltx.TXID, error) {\n\t\t\tif closeErr := itr.Close(); closeErr != nil {\n\t\t\t\tcloseErr = fmt.Errorf(\"close level %d ltx iterator: %w\", level, closeErr)\n\t\t\t\tif retErr != nil {\n\t\t\t\t\treturn currentTXID, errors.Join(retErr, closeErr)\n\t\t\t\t}\n\t\t\t\treturn currentTXID, closeErr\n\t\t\t}\n\t\t\treturn currentTXID, retErr\n\t\t}\n\n\t\tfor itr.Next() {\n\t\t\tinfo := itr.Item()\n\n\t\t\t// Skip if there's a gap at this level too.\n\t\t\tif info.MinTXID > currentTXID+1 {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Skip if already covered.\n\t\t\tif info.MaxTXID <= currentTXID {","sourceCodeStart":996,"sourceCodeEnd":1032,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/replica.go#L996-L1032","documentation":"When leaving a level's iteration, fillFollowGap closes the LTX file iterator; if itr.Close() fails, the close error is wrapped with the level number. It can be joined with the original return error via errors.Join. Iterator close errors on most replica clients surface flush/HTTP-finalization failures of the underlying listing request.","triggerScenarios":"itr.Close() returns non-nil after iterating level-1..3 listings: the client's finalization call (e.g., closing an HTTP page-stream or file handle) fails; returned directly when retErr is nil, or joined when an apply error is already being returned.","commonSituations":"Network drop exactly while closing a paginated S3 list session, storage client implementations whose Close finalizes an in-flight request, custom replica_client.go implementations with faulty Close.","solutions":["Inspect errors.Join output for both the primary error and the close error; fix the primary cause first","Check network stability to the storage backend","If using a custom ReplicaClient, make Close idempotent and ensure it does not fail on already-drained iterators","Retry the sync cycle; this is usually transient"],"exampleFix":"// before: custom client Close that errors when iterator exhausted\nfunc (c *Client) Close() error { return c.resp.Body.Close() } // resp may be nil\n// after\nfunc (c *Client) Close() error {\n    if c.resp == nil || c.resp.Body == nil { return nil }\n    return c.resp.Body.Close()\n}","handlingStrategy":"try-catch","validationCode":"// if implementing a custom client, smoke-test iterator lifecycle\nitr, err := client.LTXFiles(ctx, 0, 0, false)\nif err != nil { return err }\nfor itr.Next() { _ = itr.Item() }\nif err := itr.Err(); err != nil { return err }\nif err := itr.Close(); err != nil { return fmt.Errorf(\"iterator close not idempotent: %w\", err) }\nif err := itr.Close(); err != nil { return fmt.Errorf(\"double-close failed: %w\", err) }","typeGuard":"type safeIterator interface {\n    ltx.FileIterator\n}\n\nfunc closeQuietly(itr ltx.FileIterator) error {\n    if itr == nil { return nil }\n    return itr.Close()\n}","tryCatchPattern":"txid, err := r.fillFollowGap(ctx, f, afterTXID, gapMinTXID, pageSize)\nif err != nil {\n    for _, e := range unpackJoined(err) { // errors.Join may carry close error too\n        log.Printf(\"gap-fill component error: %v\", e)\n    }\n    // treat close-level errors as transient; retry once\n    return retryOnce(func() error { _, err = r.fillFollowGap(ctx, f, afterTXID, gapMinTXID, pageSize); return err })\n}","preventionTips":["Implement idempotent Close in custom ReplicaClient iterators","Always inspect errors.Join components to find the root cause","Keep network sessions alive; avoid aggressive idle timeouts mid-listing","Retry replication cycles on transient close/list errors"],"tags":["storage","iterator","network","replication"],"backgroundTag":"api-request-failed","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"}