nsqio/nsq · error · FatalClientErr
E_SUB_FAILED
E_SUB_FAILED
Error message
exiting
What it means
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.
Source
Thrown at nsqd/channel.go:470
c.exitMutex.RUnlock()
return errors.New("exiting")
}
err := c.put(msg)
c.exitMutex.RUnlock()
return err
}
// deferred requeue
return c.StartDeferredTimeout(msg, timeout)
}
// AddClient adds a client to the Channel's client list
func (c *Channel) AddClient(clientID int64, client Consumer) error {
c.exitMutex.RLock()
defer c.exitMutex.RUnlock()
if c.Exiting() {
return errors.New("exiting")
}
c.RLock()
_, ok := c.clients[clientID]
numClients := len(c.clients)
c.RUnlock()
if ok {
return nil
}
maxChannelConsumers := c.nsqd.getOpts().MaxChannelConsumers
if maxChannelConsumers != 0 && numClients >= maxChannelConsumers {
return fmt.Errorf("consumers for %s:%s exceeds limit of %d",
c.topicName, c.name, maxChannelConsumers)
}
c.Lock()
c.clients[clientID] = clientView on GitHub (pinned to 85cf10c09c)
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.
Defensive patterns
Strategy: retry
Try / catch
// E_SUB_FAILED ... exiting is fatal for the connection: it will be closed.
// Catch in the connection error handler, then reconnect with backoff.
for attempt := 0; attempt < maxAttempts; attempt++ {
conn, err := dialAndSubscribe(topic, channel)
if err != nil && strings.Contains(err.Error(), "E_SUB_FAILED") {
time.Sleep(backoff.ForAttempt(attempt)) // channel was mid-delete; it is recreated on next SUB
continue
}
break
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of nsqio/nsq@85cf10c09c (2026-08-16).
Data as JSON: /api/errors/881421b5b6ff268a.
Report an issue: GitHub.