Skip to content

Reliable socket cleanup, shutdown and Redis recovery

Latest

Choose a tag to compare

@github-actions github-actions released this 18 Oct 12:43

This release makes the server release sockets and shut down reliably when handlers, hooks, clients or Redis misbehave. It also fixes client channels after disconnect(), presence after slow join hooks, and how oversized messages are handled. Upgrading is recommended for every application, especially those using a Redis transport.

Behavior changes

  • A closed socket is released immediately. Before, a handler or onLeave that never settled kept the socket counted, subscribed and present forever, and disconnect never fired. Now the socket leaves its channels and presence as soon as it closes, disconnect is emitted, and the leave hooks run on the next turn without blocking the release. As a result, disconnect now fires before onLeave finishes. Messages still queued for a closed socket are dropped.
  • Oversized outbound messages no longer close recipients. A message over maxOutboundPayload used to close each recipient with 1009, so a large broadcast kicked every subscriber. It is now dropped with a single warning, and the connections stay open. An oversized acknowledgement is replaced by an error, Response too big, so the client request rejects at once. The connection is still closed with 1009 only when even that error does not fit, which takes a very long request id.
  • Presence is announced after the join hooks. A presence subscription now takes a fresh snapshot after onMemberJoin and onJoin, so a member who left during a slow onJoin is no longer re-added. With a transport, a presence subscribe now waits up to twice presenceTimeout.

Shutdown

  • close() waits up to shutdownTimeout for pending hooks and handlers, including those of sockets that closed just before shutdown, then returns even if some never settle. A hook that is still running, for example on a timer, can keep the process alive until it ends.
  • Broadcasts made while the bus is closing are dropped instead of making the Redis disconnect fail. Before, a SIGTERM could exit with code 1.
  • Slow consumers over maxBufferedAmount are terminated if they ignore the close handshake, instead of being kept for 30 seconds.
  • An invalid retry queue option now fails before any Redis connection opens. Before, boot failed and the process stayed alive after SIGTERM.

Redis and the retry queue

  • @boringnode/bus is now ^0.10.0. The retry queue now drains as soon as Redis is back, even when retryInterval is not set. A duplicate publication no longer evicts an unrelated queued message. ioredis no longer prints "Unhandled error event" during an outage.
  • retryQueue: { maxSize: undefined } keeps the default limit of 1000 instead of making the queue unbounded.
  • A zero retryInterval such as '0ms' disables the interval like false instead of failing boot.
  • The README now explains what happens during a Redis outage. With ioredis defaults, publications wait in its offline buffer and are delivered after the reconnect. Events replayed after a full Redis restart can be lost.

If you use pnpm or another package manager that does not hoist dependencies, install @boringnode/bus next to ioredis. Your config imports the transport from it:

pnpm add @boringnode/bus@^0.10.0 ioredis

Client

  • disconnect() no longer leaves stale channel instances behind. Unsubscribed channels are evicted, so socket.channel(name) returns a fresh instance after reconnecting, and handlers registered on it fire again.
  • Channels held by a React or Vue hook keep their listen(), whisper and presence callbacks across disconnect() and resubscribe on the next connection. Don't call subscribe() or unsubscribe() directly on a channel an adapter holds; remove your own handlers with stopListening() and stopListeningForWhisper().
  • useChannelEvent (React and Vue) now infers the handler payload from the event name. It used to be the union of every event of the channel.

Types and dependencies

  • @types/ws is now a dependency. Before, consumers with skipLibCheck off got TS7016, and with it on the raw connection was typed as any.
  • The optional @opentelemetry/instrumentation peer now accepts >=0.200.0 <1.0.0. It can be installed next to @adonisjs/otel.
  • Our declarations no longer require ioredis and mqtt to type-check with skipLibCheck off.

Known issue

With a Redis transport and the AdonisJS pino logger running in a worker transport, SIGTERM can occasionally leave the process running. This comes from pino's thread-stream and not from this package. Using a synchronous logger destination in production avoids it.