{"record":{"id":"5b35a7944da01bb4","repo":"kopia/kopia","slug":"error-unmapping-index-v-also-had-close-error-v","errorCode":null,"errorMessage":"error unmapping index %v (also had close error: %v)","messagePattern":"error unmapping index (.+?) \\(also had close error: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"repo/content/committed_content_index_disk_cache_unix.go","lineNumber":36,"sourceCode":"\tf, err := os.Open(filename) //nolint:gosec\n\tif err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"unable to open file despite retries\")\n\t}\n\n\tmm, err := mmap.Map(f, mmap.RDONLY, 0)\n\tif err != nil {\n\t\t_ = f.Close()\n\t\treturn nil, nil, errors.Wrap(err, \"mmap error\")\n\t}\n\n\t// On Unix, it's safe to close the FD now; the mapping remains valid.\n\tif err := f.Close(); err != nil {\n\t\t// If close fails, still return mapping, but report error on closer to surface the issue later.\n\t\tcloseErr := errors.Wrapf(err, \"error closing index %v after mmap\", filename)\n\n\t\treturn mm, func() error {\n\t\t\tif err2 := mm.Unmap(); err2 != nil {\n\t\t\t\treturn errors.Wrapf(err2, \"error unmapping index %v (also had close error: %v)\", filename, closeErr)\n\t\t\t}\n\n\t\t\treturn closeErr\n\t\t}, nil\n\t}\n\n\treturn mm, func() error {\n\t\tif err2 := mm.Unmap(); err2 != nil {\n\t\t\treturn errors.Wrapf(err2, \"error unmapping index %v\", filename)\n\t\t}\n\n\t\treturn nil\n\t}, nil\n}\n","sourceCodeStart":18,"sourceCodeEnd":51,"githubUrl":"https://github.com/kopia/kopia/blob/82495e54b584c1ef6073c9e1be048f57f8aef078/repo/content/committed_content_index_disk_cache_unix.go#L18-L51","documentation":"This error is produced by the closer function returned from mmapFile (unix) when, after the file Close already failed, the subsequent mm.Unmap() also fails. The wrap includes the earlier close error so both failures are visible in one message. It indicates the process could not release the memory mapping — usually because the mapping was already invalidated or memory pressure/OS quirks prevented munmap from succeeding.","triggerScenarios":"Calling the closer returned by openIndex on Unix when both f.Close() (reported via closeErr inside the message) and mm.Unmap() fail — e.g. double-closing the same index, or unmapping after the mapping was already torn down.","commonSituations":"Bugs where the closer is invoked twice on the same index, or teardown during abnormal shutdown when the process is being torn apart and syscalls start failing.","solutions":["Read the embedded '(also had close error: %v)' portion to diagnose the original close failure first","Ensure the closer is invoked exactly once per openIndex result; add sync.Once if double-close is possible","Ignore if occurring during process exit — the OS reclaims mappings at exit","If repeated, check for memory corruption or fs/driver issues on the cache mount"],"exampleFix":"// before: calling closer unconditionally on every path, possibly twice\ncloser(); defer cleanup()\n// after: guarantee single invocation\nvar closeOnce sync.Once\ndispose := func() { closeOnce.Do(func() { _ = closer() }) }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := closer(); err != nil {\n    if strings.Contains(err.Error(), \"unmapping index\") {\n        // likely double-invocation; make disposal idempotent\n    }\n    log.Printf(\"index teardown: %v\", err)\n}","preventionTips":["Wrap the closer in sync.Once to guarantee single invocation","Do not share one openIndex result across goroutines without synchronization","Rely on the cache API for index lifecycle instead of manual close/unmap","Inspect the embedded close error in the message before chasing the unmap failure"],"tags":["mmap","munmap","unix","resource-cleanup"],"backgroundTag":"mmap-unmap-failed","analyzedSha":"82495e54b584c1ef6073c9e1be048f57f8aef078","analyzedAt":"2026-09-07T20:35:21.689Z","contentChangedAt":"2026-09-07T20:35:21.689Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}