{"record":{"id":"a420d7c27bd58b83","repo":"slackhq/nebula","slug":"queue-set-already-closed-a420d7","errorCode":null,"errorMessage":"queue set already closed","messagePattern":"queue set already closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/tio/queueset_poll_linux.go","lineNumber":44,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to create eventfd: %w\", err)\n\t}\n\n\tout := &pollQueueSet{\n\t\tpq:         []*Poll{},\n\t\tpqi:        []Queue{},\n\t\tshutdownFd: shutdownFd,\n\t}\n\n\treturn out, nil\n}\n\nfunc (c *pollQueueSet) Queues() []Queue {\n\treturn c.pqi\n}\n\nfunc (c *pollQueueSet) Add(fd int) error {\n\tif c.closed.Load() {\n\t\treturn errors.New(\"queue set already closed\")\n\t}\n\tx, err := newPoll(fd, c.shutdownFd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc.pq = append(c.pq, x)\n\tc.pqi = append(c.pqi, x)\n\n\treturn nil\n}\n\nfunc (c *pollQueueSet) wakeForShutdown() error {\n\tvar buf [8]byte\n\tbinary.NativeEndian.PutUint64(buf[:], 1)\n\t_, err := unix.Write(int(c.shutdownFd), buf[:])\n\treturn err\n}\n","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/tio/queueset_poll_linux.go#L26-L62","documentation":"pollQueueSet (epoll/poll-based queue set) returns errors.New(\"queue set already closed\") from Add when its closed atomic flag indicates the set has been shut down, blocking registration of new file descriptors into a dead poll set. It is the poll-mode analogue of the GSO queue-set guard.","triggerScenarios":"Calling pollQueueSet.Add(fd) after Close set c.closed. Occurs when a connection fd is registered for polling while the poll set is concurrently being torn down, or when stale references to a replaced queue set are used.","commonSituations":"Graceful shutdown racing with new connection registration; hot-reload of listeners swapping queue sets; a deferred Add running after an early return path closed the set; tests closing the set before all fds are registered.","solutions":["Check closed state before Add and close the fd instead of registering it.","Guard the closed check and pq append with a mutex so Add is atomic with respect to Close.","Use a WaitGroup in Close so in-flight Adds complete before teardown finishes.","In callers, treat this error as benign during shutdown: release the fd and continue.","Introduce a package-level sentinel error for errors.Is comparisons and tests."],"exampleFix":"// before: adding without state check\nif err := pqs.Add(fd); err != nil {\n    return err\n}\n// after: guard against closed set\nselect {\ncase <-pqs.Done():\n    fd.Close()\n    return nil\ndefault:\n}\nif err := pqs.Add(fd); err != nil {\n    fd.Close()\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Go: skip registration when the poll set is shutting down\nif pqs.Closed() {\n    fd.Close()\n    return\n}","typeGuard":null,"tryCatchPattern":"if err := pqs.Add(fd); err != nil {\n    if pqs.Closed() {\n        fd.Close()\n        return nil // shutdown in progress\n    }\n    return err\n}","preventionTips":["Guard Add with a closed-state check executed atomically with the append.","Make Close wait for in-flight Adds via WaitGroup before closing shutdownFd.","Swap queue sets (reload) only after draining pending registrations.","Treat this error as benign during teardown and route it to a debug log, not alerts."],"tags":["linux","network","epoll","lifecycle","concurrency"],"backgroundTag":"queue-set-closed","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"}