nsqio/nsq · error · ClientErr
E_REQ_FAILED
E_REQ_FAILED
Error message
exiting
What it means
Channel.RequeueMessage handles a REQ with timeout 0 by immediately re-putting the message into the channel queue. Under exitMutex it first checks Exiting(): once the channel has been told to exit (topic/channel deleted via the HTTP API, empty-and-close, or nsqd shutdown) no further state mutation is allowed and it returns errors.New("exiting"). The TCP protocol layer wraps this as a non-fatal E_REQ_FAILED ('REQ <id> failed exiting') response to the client.
Source
Thrown at nsqd/channel.go:453
//
// `timeoutMs` == 0 - requeue a message immediately
// `timeoutMs` > 0 - asynchronously wait for the specified timeout
//
// and requeue a message (aka "deferred requeue")
func (c *Channel) RequeueMessage(clientID int64, id MessageID, timeout time.Duration) error {
// remove from inflight first
msg, err := c.popInFlightMessage(clientID, id)
if err != nil {
return err
}
c.removeFromInFlightPQ(msg)
atomic.AddUint64(&c.requeueCount, 1)
if timeout == 0 {
c.exitMutex.RLock()
if c.Exiting() {
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")
}View on GitHub (pinned to 85cf10c09c)
Solutions
- Treat E_REQ_FAILED ... 'exiting' as terminal for that channel: stop REQ-retrying and let the client disconnect/reconnect.
- Resubscribe after reconnect - if the channel was merely deleted it is recreated on SUB, and the message was already requeued server-side.
- Coordinate: pause the channel (POST /channel/pause) before deleting so consumers stop receiving, avoiding the race.
- During nsqd shutdown, CLS/close consumer connections before deleting channels.
Defensive patterns
Strategy: try-catch
Try / catch
// consumer side (go-nsq style): inspect the error frame from REQ
err := consumer.HandleReq(id, timeout) // or your raw-command wrapper
if err != nil {
if strings.Contains(err.Error(), "E_REQ_FAILED") && strings.Contains(err.Error(), "exiting") {
// channel/topic is being deleted or nsqd is shutting down: stop retrying,
// the message was already requeued server-side
return errStopConsuming
}
return err
} Prevention
- Pause channels before deleting them (POST /channel/pause?topic=t&channel=c) so REQ/SUB races cannot occur.
- Make consumers treat any E_*_FAILED containing 'exiting' as a reconnect signal, not a data error.
- Avoid running channel/topic deletion automation while consumers are actively subscribed.
When it happens
Trigger: A consumer sends REQ <id> 0 at the same moment the channel is being deleted: DELETE /channel/delete?topic=t&channel=c, DELETE /topic/delete, or nsqd draining during shutdown. The message was already popped from the in-flight map, so the REQ cannot be honored and the client gets E_REQ_FAILED.
Common situations: Ops runbooks that delete/recreate channels or topics while consumers are live; rolling redeployments that empty channels; consumers with long max-requeue-timeouts racing a channel teardown; nsqd graceful shutdown with connected stragglers.
Related errors
AI-assisted analysis of nsqio/nsq@85cf10c09c (2026-08-16).
Data as JSON: /api/errors/18f90dd7b28c41a1.
Report an issue: GitHub.