{"record":{"id":"de9d60da224a1651","repo":"hashicorp/nomad","slug":"failed-to-unmount-ns-at-s-w","errorCode":null,"errorMessage":"failed to unmount NS: at %s: %w","messagePattern":"failed to unmount NS: at (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/lib/nsutil/netns_linux.go","lineNumber":136,"sourceCode":"\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to bind mount ns at %s: %v\", nsPath, err)\n\t\t}\n\t})()\n\twg.Wait()\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create namespace: %v\", err)\n\t}\n\n\treturn GetNS(nsPath)\n}\n\n// UnmountNS unmounts the NS held by the netns object\nfunc UnmountNS(nsPath string) error {\n\t// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)\n\tif strings.HasPrefix(nsPath, NetNSRunDir) {\n\t\tif err := unix.Unmount(nsPath, unix.MNT_DETACH); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to unmount NS: at %s: %w\", nsPath, err)\n\t\t}\n\n\t\tif err := os.Remove(nsPath); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to remove ns path %s: %w\", nsPath, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n","sourceCodeStart":118,"sourceCodeEnd":146,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/lib/nsutil/netns_linux.go#L118-L146","documentation":"UnmountNS tears down a namespace mount created by NewNS by calling umount2 with MNT_DETACH on the path. If the unmount syscall fails, this error is returned. It means the bind mount under /var/run/netns could not be detached, typically because it is busy or the caller lacks privileges.","triggerScenarios":"UnmountNS (via DestroyNetwork) is called with a path under /var/run/netns and unix.Unmount(nsPath, MNT_DETACH) returns an error — EBUSY when the mount is in use, EPERM without privileges, EINVAL when the path is not a mountpoint.","commonSituations":"Processes (veth handles, test binaries) still running inside the namespace keeping the mount busy; calling DestroyNetwork twice with a stale path; path passed is not actually a mountpoint (already unmounted).","solutions":["Ensure no processes remain in the namespace before destroying it (kill containers/test processes first)","Check for leftover mounts: grep netns /proc/mounts and umount manually if needed","If EINVAL because it is not a mountpoint, treat the path as already unmounted and just remove the file","Run with sufficient privileges (CAP_SYS_ADMIN) for umount"],"exampleFix":"// before: double teardown triggers EINVAL/EBUSY\nerr := nsutil.UnmountNS(ns.Path())\n// after: guard against double-unmount\nif _, statErr := os.Stat(ns.Path()); statErr == nil {\n    if out, err := exec.Command(\"sh\", \"-c\", \"grep -qs \"+ns.Path()+\" /proc/mounts\").Output(); err == nil && len(out) > 0 {\n        err = nsutil.UnmountNS(ns.Path())\n    }\n}","handlingStrategy":"try-catch","validationCode":"out, _ := os.ReadFile(\"/proc/mounts\")\nif !strings.Contains(string(out), \"/run/netns/\") {\n    // nothing mounted at this path; skip unmount\n}","typeGuard":"func isMountedUnderRunNetns(nsPath string) bool {\n    return strings.HasPrefix(nsPath, \"/run/netns/\")\n}","tryCatchPattern":"err := nsutil.UnmountNS(nsPath)\nif err != nil {\n    var errno unix.Errno\n    if errors.As(err, &errno) && (errno == unix.EINVAL || errno == unix.EBUSY) {\n        log.Printf(\"ns mount busy/absent at %s, cleaning up processes and retrying\", nsPath)\n    }\n    return err\n}","preventionTips":["Terminate all processes inside the namespace before calling UnmountNS","Make network teardown idempotent (tolerate not-a-mountpoint)","Run teardown with CAP_SYS_ADMIN","Clean stale entries in /run/netns during startup"],"tags":["linux","network-namespace","unmount","cleanup"],"backgroundTag":"umount-busy","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"}