Skip to content

fix: remove custom server upgrade listener on close - #379

Open
lprnmns wants to merge 1 commit into
fastify:mainfrom
lprnmns:fix/remove-custom-server-upgrade-listener
Open

lprnmns wants to merge 1 commit into
fastify:mainfrom
lprnmns:fix/remove-custom-server-upgrade-listener

Conversation

@lprnmns

@lprnmns lprnmns commented Aug 31, 2026

Copy link
Copy Markdown

Problem

When @fastify/websocket is configured with options.server, it registers its upgrade listener on that supplied server. On fastify.close(), cleanup currently removes the listener from fastify.server instead. If the supplied server remains running, later upgrade requests are routed through the closed Fastify instance and receive a stale 503 response.

Fix

Remove the plugin upgrade listener from websocketListenServer, the same HTTP/S server selected during setup. This preserves unrelated upgrade listeners on a shared server.

Tests

  • node --test --test-name-pattern="custom server on close" test/base.test.js — failed before the fix with 1 !== 0; passed after the fix (1 test)
  • npm test — passed; 61 unit tests, 100% reported coverage, 65 TypeScript assertions
  • npm run lint — passed
  • node --check index.js && node --check test/base.test.js && git diff --check — passed
  • npm run benchmark --if-present — passed; no benchmark script is defined

Compatibility

This changes cleanup only for the supported pre-created HTTP/S server option. It does not change route handling or remove listeners owned by other consumers.

Related issue

Independent current-branch reproduction; no existing issue or pull request matched after searches for custom server, external server, server option, upgrade listener, listener cleanup, and Fastify close behavior.

Signed-off-by: lprnmns <manasalperen@gmail.com>
@lprnmns
lprnmns marked this pull request as ready for review August 31, 2026 16:20
@aoede3

aoede3 commented Sep 8, 2026

Copy link
Copy Markdown

Independent confirmation of this from a different symptom path.

Setup: a never-listened http.createServer() passed as options.server, alongside another upgrade consumer on the Fastify server, plugin 11.3.0. Checked listenerCount('upgrade') on the supplied server at each stage of the lifecycle:

before register = 0
after ready = 1
after app.close() = 1

So the listener installed by the plugin survives close. With a custom preClose that also does server.removeListener('upgrade', ...) on the supplied server the count returns to 0, and it stays exact 0 -> 1 -> 0 across two boot/close cycles. That matches this PR's change of removing from websocketListenServer rather than fastify.server, and I'd expect the default preClose to give the same 0 with it applied.

Measured while running the plugin alongside Vite's HMR websocket in custom dev setup; with this fix that setup needs no custom preClose.

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

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.

3 participants