Title: AMQP reconnections don't clean up `sync-out` event handlers, causing memory leaks · Issue #202 · feathersjs/feathers-sync · GitHub
Open Graph Title: AMQP reconnections don't clean up `sync-out` event handlers, causing memory leaks · Issue #202 · feathersjs/feathers-sync
X Title: AMQP reconnections don't clean up `sync-out` event handlers, causing memory leaks · Issue #202 · feathersjs/feathers-sync
Description: Background I just had a deep dive into some memory issues of our application. It kept growing over time and we couldn't pinpoint what was causing it. Eventually we saw thousands of references of 131 kB to an instance of Mux from amqplib ...
Open Graph Description: Background I just had a deep dive into some memory issues of our application. It kept growing over time and we couldn't pinpoint what was causing it. Eventually we saw thousands of references of 13...
X Description: Background I just had a deep dive into some memory issues of our application. It kept growing over time and we couldn't pinpoint what was causing it. Eventually we saw thousands of references o...
Opengraph URL: https://github.com/feathersjs/feathers-sync/issues/202
X: @github
Domain: patch-diff.githubusercontent.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"AMQP reconnections don't clean up `sync-out` event handlers, causing memory leaks","articleBody":"### Background\n\nI just had a deep dive into some memory issues of our application. It kept growing over time and we couldn't pinpoint what was causing it. Eventually we saw thousands of references of 131 kB to an instance of `Mux` from `amqplib` (amounting to ~2.1GB of unnecessary memory). This led led us to `feathers-sync`, as we use it with the AMQP adapter.\n\n#### Update 2025-09-25\nSee my [comment](https://github.com/feathersjs/feathers-sync/issues/202#issuecomment-3258000655) below\n\n### Issue\n\nThe AMQP adapter doesn't properly clean up the registered `app.on('sync-out', handlerFn)` event handler when a channel is closed. The `handlerFn` contains a reference to the `channel` which is never cleaned up, as the event listener stays active even after a connection is closed.\n\nThe AMQP adapter uses `amqp-connection-manager` for automatic reconnections. The `setup` option of `createChannel` ([related feathers-sync code](https://github.com/feathersjs/feathers-sync/blob/b778f03ebff718b4f3b27e8167c5c8c6be5837ec/lib/adapters/amqp.js#L16-L56)) is called each time a new connection is made. \n`feathers-sync` sets up an event listener to `sync-out` to publish the event to the queue within that `setup` function. However, in the event of a channel closing (due to whatever reason), the event listener is never cleaned up. Upon new events it'll keep executing the now redundant event listener, as the channel can't be published to anymore.\n\nSee the below image for an example of the issue. I added some debug logs to illustrate when a channel is reconnected / closed, as well as which events are sent on which pid / channel. For this I also had to make a small change in `amqplib` to attach a random string to each created channel.\n\nScenario: I make a change to an item, wait for the event to be sent, and then I restart my AMQP Docker container to trigger a reconnection.\n\n\u003cimg width=\"593\" height=\"486\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/a1956dd4-f13e-49e6-8fc4-5c11a9547646\" /\u003e\n\n### Impact\n \n* Memory leaks\n * The `channel` reference (and all other references within it) stay in memory\n* Unnecessary processing, as it'll keep trying to call `channel.publish` on a closed channel, which will just error\n\n### Reproduction\n\n1. Setup a Docker container with AMQP (or run it locally)\n2. Run a Feathers app with the AMQP adapter for `feathers-sync` using `DEBUG=feathers-sync:amqp` for debug logs\n3. Trigger something that causes events to be sent via `feathers-sync`\n4. Observe how only one \"Publish success: |true|\" debug is logged\n5. Restart AMQP\n6. Trigger the same thing again\n7. Observe how there is now also a \"Publish fail: |Channel closed|\" log alongside the \"Publish success: |true|\" log\n8. Repeat however many times as you want. Each restart of AMQP will add another \"Publish fail: |Channel closed|\" log\n\n### Proposed solution\n\nUnregister the event listener when a channel is closed. From what I can gather, you can listen to `close` events on the channel itself. The following seems to fix it for me:\n\n```diff\n- // Publish the received message to the queue\n- app.on('sync-out', (data) =\u003e {\n- try {\n- const publishResponse = channel.publish(\n- key,\n- queue,\n- Buffer.from(data)\n- )\n- debug(`Publish success: |${publishResponse}| APMQ channel`)\n- } catch (error) {\n- debug(`Publish fail: |${error.message}| APMQ channel`)\n- }\n- })\n\n+ function publishToQueue(data) {\n+ try {\n+ const publishResponse = channel.publish(\n+ key,\n+ queue,\n+ Buffer.from(data)\n+ )\n+ debug(`Publish success: |${publishResponse}| APMQ channel`)\n+ } catch (error) {\n+ debug(`Publish fail: |${error.message}| APMQ channel`)\n+ }\n+ }\n\n+ // Publish the received message to the queue\n+ app.on('sync-out', publishToQueue)\n\n+ channel.on('close', () =\u003e {\n+ debug(Channel closed')\n+ app.off('sync-out', publishToQueue)\n+ })\n+ })\n```\n\nSide note: all the debug logs in the AMQP adapter have a typo: `APMQ` should be `AMQP`","author":{"url":"https://github.com/JulienZD","@type":"Person","name":"JulienZD"},"datePublished":"2025-08-26T10:37:35.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":3},"url":"https://github.com/202/feathers-sync/issues/202"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:9a62db7a-738c-ba14-c71b-4811bb4b3df1 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 93D2:FC3F2:9822398:C967FDC:696E034B |
| html-safe-nonce | e407ba419493d42275f7fa32dd4babb5c046654ed2efb46b1d7f00ffe2cc5abf |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5M0QyOkZDM0YyOjk4MjIzOTg6Qzk2N0ZEQzo2OTZFMDM0QiIsInZpc2l0b3JfaWQiOiI2ODAzMjA1MDQwMTY5MTU3NDUyIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 6d77bbbbe3d6ee30ded235fbf91b3204245f8f185dc63e7b8a45cde11fa8ed10 |
| hovercard-subject-tag | issue:3355098468 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/feathersjs/feathers-sync/202/issue_layout |
| twitter:image | https://opengraph.githubassets.com/82d36c27b142a04253f3b4657357c1ec940b1ee5952fc6448105921e2cdfa453/feathersjs/feathers-sync/issues/202 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/82d36c27b142a04253f3b4657357c1ec940b1ee5952fc6448105921e2cdfa453/feathersjs/feathers-sync/issues/202 |
| og:image:alt | Background I just had a deep dive into some memory issues of our application. It kept growing over time and we couldn't pinpoint what was causing it. Eventually we saw thousands of references of 13... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | JulienZD |
| hostname | github.com |
| expected-hostname | github.com |
| None | 9b5131b207ddd175abf059a848d5f4302ec0606b02211b989013be49cf08593e |
| turbo-cache-control | no-preview |
| go-import | github.com/feathersjs/feathers-sync git https://github.com/feathersjs/feathers-sync.git |
| octolytics-dimension-user_id | 5321853 |
| octolytics-dimension-user_login | feathersjs |
| octolytics-dimension-repository_id | 32042756 |
| octolytics-dimension-repository_nwo | feathersjs/feathers-sync |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 32042756 |
| octolytics-dimension-repository_network_root_nwo | feathersjs/feathers-sync |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | f8590a63bfc8093b241930ca57d536c9a50f9680 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width