googleapis / googleapis/google-cloud-node
`AsyncLocalStorage` context not exiting properly when `.run()` called in `.on('message')` handler
- Dominant language
- TypeScript
- Stars
- 3.2k
- Forks
- 712
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 99
Description
### Please make sure you have searched for information in the following guides.
- [x] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues
- [x] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js
- [x] Check our Troubleshooting guide: https://github.com/googleapis/google-cloud-node/blob/main/docs/troubleshooting.md
- [x] Check our FAQ: https://github.com/googleapis/google-cloud-node/blob/main/docs/faq.md
- [x] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md
- [x] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs
- [x] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples
### A screenshot that you have tested with "Try this API".
This problem applies specifically to the Node.js client in combination with AsyncLocalStorage. It's not an issue with the API itself.
### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction.
https://github.com/swarmiakimmo/pubsub-async-local-storage-repro
### A step-by-step description of how to reproduce the issue, based on the linked reproduction.
Running in Node v22.16.0.
1. Edit `projectId`, `topicName`, and `subscriptionName` in index.js. They need to refer to a GCP project you have access to, an existing topic name and subscription name.
1. `npm install`
1. Run the example script: `GOOGLE_APPLICATION_CREDENTIALS=/Users/kimmo/.gcloud/ node index.js`
### A clear and concise description of what the bug is, and what you expected to happen.
**The issue**
Once you run the example script, these see these log lines appear:
```
ERROR: operation context already has an ID: 412ebf00-3e8f-4ab1-8ae5-f001ebb97229
```
which means that the `AsyncLocalStorage` context _already_ had a value when it entered the `.on('message', ...)` callback handler.
The fact that previous context is somehow visible to the handler at that point also hints that there could be a potential memory leak when PubSub client is used together with `AsyncLocalStorage`. The contexts "stack up", so it's possible to have multiple nested `.run()` calls with their own isolated store. If the handler sees the context value at that point in the handler _(instead of it being cleared)_, something seems to retain the reference to the ALS.
**Expected result**
At the beginning of `on('message', ...)` handler, the `als.getStore()` should return `undefined` because we should be processing another message in another callback context.
Whenever `.run()` callback function exits, also the context should be exited as stated in [Node.js API docs](https://nodejs.org/docs/latest-v22.x/api/async_context.html#asynclocalstoragerunstore-callback-args):
> Runs a function synchronously within a context and returns its return value. **The store is not accessible outside of the callback function.** The store is accessible to any asynchronous operations created within the callback.
### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. **
The expected behavior I described is how `AsyncLocalStorage` should work by standard. There's something that the PubSub Node.js internally does to break the normal expected behavior.
Contributor guide
Assessment
This issue has not been assessed yet.