Skip to content

AsyncLocalStorage context not exiting properly when .run() called in .on('message') handler #2052

@swarmiakimmo

Description

@swarmiakimmo

Please make sure you have searched for information in the following guides.

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.
  2. npm install
  3. Run the example script: GOOGLE_APPLICATION_CREDENTIALS=/Users/kimmo/.gcloud/<INSERT_YOUR_JSON_KEY_PATH> 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:

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.

Metadata

Metadata

Assignees

Labels

api: pubsubIssues related to the googleapis/nodejs-pubsub API.priority: p2Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions