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

  1. Treat E_REQ_FAILED ... 'exiting' as terminal for that channel: stop REQ-retrying and let the client disconnect/reconnect.
  2. Resubscribe after reconnect - if the channel was merely deleted it is recreated on SUB, and the message was already requeued server-side.
  3. Coordinate: pause the channel (POST /channel/pause) before deleting so consumers stop receiving, avoiding the race.
  4. 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

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.