{"record":{"id":"ec0ffd5a729dc7a2","repo":"hashicorp/nomad","slug":"q-has-already-been-closed","errorCode":null,"errorMessage":"%q has already been closed","messagePattern":"%q has already been closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/lib/nsutil/ns_linux.go","lineNumber":169,"sourceCode":"\tfd, err := os.Open(nspath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &netNS{file: fd}, nil\n}\n\nfunc (ns *netNS) Path() string {\n\treturn ns.file.Name()\n}\n\nfunc (ns *netNS) Fd() uintptr {\n\treturn ns.file.Fd()\n}\n\nfunc (ns *netNS) errorIfClosed() error {\n\tif ns.closed {\n\t\treturn fmt.Errorf(\"%q has already been closed\", ns.file.Name())\n\t}\n\treturn nil\n}\n\nfunc (ns *netNS) Do(toRun func(NetNS) error) error {\n\tif err := ns.errorIfClosed(); err != nil {\n\t\treturn err\n\t}\n\n\tcontainedCall := func(hostNS NetNS) error {\n\t\tthreadNS, err := GetCurrentNS()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open current netns: %v\", err)\n\t\t}\n\t\tdefer threadNS.Close()\n\n\t\t// switch to target namespace\n\t\tif err = ns.Set(); err != nil {","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/lib/nsutil/ns_linux.go#L151-L187","documentation":"errorIfClosed guards every operation on a netNS handle; this error is returned when Set(), Do(), or any use after Close() is attempted on an already-closed NetNS. Once closed, the underlying file is closed and the handle cannot be reused.","triggerScenarios":"Calling Set() or Do() on a NetNS after calling Close() on it; calling Set() on the saved hostNS/threadNS handle after a deferred Close() already ran; reusing a NetNS obtained from GetNS/GetCurrentNS after the defer cleanup fired.","commonSituations":"Deferred Close() firing before a later goroutine/closure uses the handle; calling Do() twice where the first defer closed the handle; storing a NetNS long-term and using it after a shutdown path closed it.","solutions":["Do not call Close() until all Set()/Do() usage of the handle is finished","Re-open the namespace with GetNS(nspath) or GetCurrentNS() instead of reusing the closed handle","Audit defer ordering so Close() is the last action referencing the handle","Guard concurrent use with ownership/lifetime management rather than sharing the handle across goroutines after close"],"exampleFix":"// before\nhostNS, _ := GetCurrentNS()\ndefer hostNS.Close()\n// ... later in another goroutine\nhostNS.Set() // \"has already been closed\"\n// after\nhostNS, _ := GetCurrentNS()\nerr := doWork(hostNS) // all use happens here\ndefer hostNS.Close()  // close only after last use","handlingStrategy":"validation","validationCode":"func ensureOpen(ns *netNS) error {\n    if ns == nil || ns.closed {\n        return errors.New(\"netns closed: reopen with GetNS/GetCurrentNS before use\")\n    }\n    return nil\n}","typeGuard":"func isOpen(ns NetNS) bool {\n    n, ok := ns.(*netNS)\n    return ok && !n.closed\n}","tryCatchPattern":"if err := ns.Set(); err != nil {\n    if strings.Contains(err.Error(), \"has already been closed\") {\n        ns, err = GetNS(nsPath) // reopen instead of failing\n        if err != nil { return err }\n        return ns.Set()\n    }\n    return err\n}","preventionTips":["Close NetNS handles only after all Set()/Do() usage completes","Order defers so Close() runs last","Do not share a NetNS across goroutines after initiating teardown","Reopen via GetNS instead of caching handles long-term"],"tags":["linux","network-namespace","use-after-close","lifecycle"],"backgroundTag":"use-after-close","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}