Skip to content

Fix: consumer.wait() resolves cleanly on client-initiated close#196

Draft
baelter wants to merge 1 commit intomainfrom
fix/consumer-wait-resolves-on-graceful-client-close
Draft

Fix: consumer.wait() resolves cleanly on client-initiated close#196
baelter wants to merge 1 commit intomainfrom
fix/consumer-wait-resolves-on-graceful-client-close

Conversation

@baelter
Copy link
Copy Markdown
Member

@baelter baelter commented Mar 18, 2026

Problem

When client.close() is called, AMQPChannel.setClosed() passes an error to all consumers — even though the close was client-initiated and graceful. This causes consumer.wait() to reject with "Connection closed by client" instead of resolving.

Callers are then forced to catch this error and distinguish it from actual failures:

try {
  await consumer.wait()
} catch (err) {
  // Is this a real error, or just our own close()?
  if (err.message === "Connection closed by client") return
  throw err
}

This is especially problematic during graceful shutdown, where there's a race between when isShuttingDown is set and when the TCP Close-Ok event fires, making reliable detection timing-dependent.

Fix

In setClosed(), only pass the error to consumers when the server closed the channel/connection. For client-initiated close (closedByServer === false), pass undefined so consumer.wait() resolves normally.

RPC callbacks and unconfirmed publishes still reject — those represent in-flight operations that didn't complete and callers need to know about.

Before / After

// Before: always rejects
await consumer.wait()  // throws Error("Connection closed by client")

// After: resolves cleanly on client.close(), rejects on server close
await consumer.wait()  // resolves

When client.close() is called, setClosed() was passing an error to all
consumers, causing consumer.wait() to reject with "Connection closed by
client". This forced callers to catch and distinguish this expected
condition from actual errors.

Only propagate the error to consumers when the server closed the
channel/connection. For client-initiated close, pass undefined so
consumer.wait() resolves normally, matching the expected graceful
shutdown contract.

RPC callbacks and unconfirmed publishes still reject (they represent
in-flight operations that didn't complete).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant