{"record":{"id":"bb3a34b2e6b44593","repo":"slackhq/nebula","slug":"failed-to-create-shutdown-pipe-w","errorCode":null,"errorMessage":"failed to create shutdown pipe: %w","messagePattern":"failed to create shutdown pipe: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tun_freebsd.go","lineNumber":310,"sourceCode":"\t}\n\tif errors.Is(err, fs.ErrNotExist) || deviceName == \"\" {\n\t\t// If the device doesn't already exist, request a new one and rename it\n\t\tfd, err = unix.Open(\"/dev/tun\", os.O_RDWR, 0)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = unix.SetNonblock(fd, true); err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, fmt.Errorf(\"failed to set tun device as nonblocking: %w\", err)\n\t}\n\n\t// Shutdown pipe lets Close wake any reader/writer blocked in Poll.\n\tvar pipeFds [2]int\n\tif err = unix.Pipe2(pipeFds[:], unix.O_CLOEXEC|unix.O_NONBLOCK); err != nil {\n\t\t_ = unix.Close(fd)\n\t\treturn nil, fmt.Errorf(\"failed to create shutdown pipe: %w\", err)\n\t}\n\tshutdownR, shutdownW := pipeFds[0], pipeFds[1]\n\n\tcloseOnErr := true\n\tdefer func() {\n\t\tif closeOnErr {\n\t\t\t_ = unix.Close(fd)\n\t\t\t_ = unix.Close(shutdownR)\n\t\t\t_ = unix.Close(shutdownW)\n\t\t}\n\t}()\n\n\t// Read the name of the interface\n\tvar name [16]byte\n\targ := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)}\n\tctrlErr := ioctl(uintptr(fd), FIODGNAME, uintptr(unsafe.Pointer(&arg)))\n\n\tif ctrlErr == nil {","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tun_freebsd.go#L292-L328","documentation":"newTun creates a nonblocking CLOEXEC pipe used as a shutdown signal: Close() writes to it so goroutines blocked in Poll wake up. If unix.Pipe2 with O_CLOEXEC|O_NONBLOCK fails, the tun fd is closed and this wrapped error is returned. Without the pipe, graceful shutdown of the device reader would hang, so construction is aborted.","triggerScenarios":"unix.Pipe2(pipeFds[:], unix.O_CLOEXEC|unix.O_NONBLOCK) fails during newTun — practically always due to hitting the process or system file descriptor limit (EMFILE/ENFILE), or an old kernel lacking pipe2.","commonSituations":"Long-running daemons leaking fds until pipe() returns EMFILE; heavily loaded hosts at the kernel file-max; running on stripped-down FreeBSD environments where pipe2 support or fd limits are constrained.","solutions":["Free file descriptors / fix the fd leak; check with lsof or procstat how many fds the process holds","Raise the soft RLIMIT_NOFILE (ulimit -n) for the daemon","Check the wrapped errno to distinguish EMFILE (per-process) from ENFILE (system-wide)","Retry device creation after shedding load"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"var rl syscall.Rlimit\nsyscall.Getrlimit(syscall.RLIMIT_NOFILE, &rl)\nif int(rl.Cur)-countOpenFds() < 8 {\n    return errors.New(\"too few free file descriptors to create tun + shutdown pipe\")\n}","typeGuard":null,"tryCatchPattern":"tunDev, err := newTun(cfg, log, prefixes, false)\nif err != nil && strings.Contains(err.Error(), \"failed to create shutdown pipe\") {\n    log.Error(\"pipe creation failed (likely fd exhaustion)\", \"err\", err)\n    // alert/rotate, then retry after fds are freed\n}","preventionTips":["Fix fd leaks; audit with lsof/procstat on long-running processes","Raise ulimit -n for the daemon's service unit","Distinguish EMFILE (process) vs ENFILE (system) from the wrapped errno","Keep shutdown-pipe creation close to device open to limit fd pressure window"],"tags":["freebsd","tun","pipe","file-descriptor","shutdown"],"backgroundTag":"fd-exhaustion","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}