Skip to content

[fix][auth] Allow natural exit with token suppliers - #510

Open
equanz wants to merge 3 commits into
apache:masterfrom
equanz:fix/token-supplier-shutdown
Open

equanz wants to merge 3 commits into
apache:masterfrom
equanz:fix/token-supplier-shutdown

Conversation

@equanz

@equanz equanz commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Using a token supplier can keep Node.js running after client.close() completes.
The supplier's thread-safe function keeps the event loop alive while the Authentication object remains reachable.

Allowing natural process exit also requires protecting native logging from accessing an already finalized thread-safe function during shutdown.

Modifications

  • Unref the supplier's thread-safe function so it allows natural process exit
  • Keep callback state alive through finalization and avoid releasing an already finalized thread-safe function
  • Clear the registered logging callback during finalization and synchronize native logging with finalization to prevent access to a finalized thread-safe function

Verifying this change

  • Make sure that the change passes the CI checks.

This change added tests and can be verified as follows:

  • Add process-exit regression tests for synchronous and asynchronous suppliers

Documentation

  • doc-required
    (Your PR needs to update docs and you will update later)

  • doc-not-needed
    Fixes process shutdown behavior without changing the public API or configuration.

  • doc
    (Your PR contains doc changes)

  • doc-complete
    (Docs have been already added)

Token supplier callbacks keep the event loop alive even after a client
is closed. Allow natural exit while preserving callback state through
thread-safe function finalization.
@equanz equanz closed this Oct 15, 2026
@equanz equanz reopened this Oct 15, 2026
@equanz
equanz marked this pull request as draft October 19, 2026 09:11
Native logging can access the thread-safe function after it has been
finalized. Clear the registered callback during finalization and
synchronize native access to prevent use after finalization.
@equanz
equanz marked this pull request as ready for review October 23, 2026 10:47

@codelipenghui codelipenghui left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review request for changes for commit d6e52aa.

Summary:

The change addresses retained-authentication liveness and improves logger ownership, but unreferencing the supplier exposes an unguarded native-callback/finalization race. Request changes for this lifetime issue. Focused local tests could not start because Jest is missing.

Comment thread src/Authentication.cc
Comment on lines +139 to +141
[tokenSupplier](Napi::Env) { tokenSupplier->finalized = true; });
this->tokenSupplier->callback = std::move(callback);
this->tokenSupplier->callback.Unref(env);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: P1

Title: Synchronize supplier finalization with native callback use

Location: src/Authentication.cc:139–141

Trigger: An idle, live producer remains reachable as the last Node event-loop handle drains, while its native connection handles a broker authentication-refresh challenge or reconnects. These background native operations do not create a ThreadSafeDeferred to keep Node alive.

Impact: The unconditional Unref permits environment teardown while a native thread is using the supplier's thread-safe function. That thread can access a freed N-API handle, causing a use-after-free or process crash during natural shutdown. Shared ownership preserves the callback container, not the underlying N-API handle.

Evidence: The finalizer only sets finalized, and only the Authentication destructor reads that flag. [TokenSupplier](<apache/pulsar-client-node/pr-510/src/Authentication.cc#L60-L76>) still calls Acquire, BlockingCall and Release without synchronization with finalization. In [Node 18's implementation](<https://raw.githubusercontent.com/nodejs/node/v18.20.8/src/node_api.cc>), environment Cleanup closes the TSFN regardless of thread_count, and Finalize deletes it after invoking the addon finalizer. Consequently, a native caller can successfully Acquire, be interrupted by environment finalization, then resume BlockingCall on the freed handle. This is reachable through [Pulsar's native authentication-challenge handling](<https://raw.githubusercontent.com/apache/pulsar-client-cpp/v4.2.0/lib/ClientConnection.cc>) and [token-supplier invocation](<https://raw.githubusercontent.com/apache/pulsar-client-cpp/v4.2.0/lib/auth/AuthToken.cc>), independently of a pending JavaScript operation. Previously, the referenced supplier prevented this natural teardown while authentication remained reachable.

Suggested fix: Coordinate supplier shutdown with native callers: prevent new calls and safely finish or cancel outstanding work before the TSFN is destroyed, or retain event-loop liveness until native users have stopped. Protect native use, not just the Authentication destructor. Add a process-exit regression involving an invoked supplier and background authentication refresh or reconnect.

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.

2 participants