{"record":{"id":"881421b5b6ff268a","repo":"nsqio/nsq","slug":"e-sub-failed","errorCode":"E_SUB_FAILED","errorMessage":"exiting","messagePattern":"exiting","errorType":"exception","errorClass":"FatalClientErr","httpStatus":null,"severity":"error","filePath":"nsqd/channel.go","lineNumber":470,"sourceCode":"\t\t\tc.exitMutex.RUnlock()\n\t\t\treturn errors.New(\"exiting\")\n\t\t}\n\t\terr := c.put(msg)\n\t\tc.exitMutex.RUnlock()\n\t\treturn err\n\t}\n\n\t// deferred requeue\n\treturn c.StartDeferredTimeout(msg, timeout)\n}\n\n// AddClient adds a client to the Channel's client list\nfunc (c *Channel) AddClient(clientID int64, client Consumer) error {\n\tc.exitMutex.RLock()\n\tdefer c.exitMutex.RUnlock()\n\n\tif c.Exiting() {\n\t\treturn errors.New(\"exiting\")\n\t}\n\n\tc.RLock()\n\t_, ok := c.clients[clientID]\n\tnumClients := len(c.clients)\n\tc.RUnlock()\n\tif ok {\n\t\treturn nil\n\t}\n\n\tmaxChannelConsumers := c.nsqd.getOpts().MaxChannelConsumers\n\tif maxChannelConsumers != 0 && numClients >= maxChannelConsumers {\n\t\treturn fmt.Errorf(\"consumers for %s:%s exceeds limit of %d\",\n\t\t\tc.topicName, c.name, maxChannelConsumers)\n\t}\n\n\tc.Lock()\n\tc.clients[clientID] = client","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/nsqio/nsq/blob/85cf10c09c6c3c86160d6f0eb156f62d0efc1648/nsqd/channel.go#L452-L488","documentation":"Channel.AddClient is invoked on SUB to register the consumer in the channel's client table. It takes exitMutex.RLock and refuses with errors.New(\"exiting\") when the channel is mid-teardown (deleted topic/channel or nsqd shutdown) because registering a client on a dying channel would corrupt accounting. protocol_v2 wraps it as a fatal E_SUB_FAILED ('SUB failed exiting'), which closes the TCP connection.","triggerScenarios":"A client sends SUB <topic> <channel> concurrently with DELETE /channel/delete or DELETE /topic/delete for that channel, or during nsqd's exit path. Because it is NewFatalClientErr, the connection is dropped immediately after the error frame, unlike non-fatal errors.","commonSituations":"Consumers auto-reconnecting in a loop while ops recreates a channel/topic; deployment scripts that delete-then-recreate topics with live subscribers; restart storms where every consumer re-SUBs exactly as channels are torn down.","solutions":["Handle connection close after E_SUB_FAILED with reconnect + resubscribe and backoff; the recreated channel accepts the next SUB.","Pause the topic/channel before deleting it so consumers quiesce first (POST /channel/pause).","Prefer emptying (POST /channel/empty) over delete when the goal is only to drain messages.","Make consumer reconnect loops tolerate a burst of E_SUB_FAILED during planned topology changes instead of crashing."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// E_SUB_FAILED ... exiting is fatal for the connection: it will be closed.\n// Catch in the connection error handler, then reconnect with backoff.\nfor attempt := 0; attempt < maxAttempts; attempt++ {\n\tconn, err := dialAndSubscribe(topic, channel)\n\tif err != nil && strings.Contains(err.Error(), \"E_SUB_FAILED\") {\n\t\ttime.Sleep(backoff.ForAttempt(attempt)) // channel was mid-delete; it is recreated on next SUB\n\t\tcontinue\n\t}\n\tbreak\n}","preventionTips":["Use a client library with built-in reconnect (go-nsq) instead of raw connections; E_SUB_FAILED exiting is transient.","Sequence topology changes: pause -> drain -> delete, so re-SUB storms never hit a dying channel.","Alert on repeated E_SUB_FAILED across many consumers - it usually means an ops script is thrashing channels."],"tags":["nsq","nsqd","protocol","lifecycle","concurrency"],"backgroundTag":null,"analyzedSha":"85cf10c09c6c3c86160d6f0eb156f62d0efc1648","analyzedAt":"2026-08-16T00:53:05.009Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}