Skip to content

Fix race condition in ShutdownListener constructor#3158

Merged
jviau merged 1 commit into
Azure:devfrom
matt-richardson:fix/shutdown-listener-race-condition
Oct 15, 2025
Merged

Fix race condition in ShutdownListener constructor#3158
jviau merged 1 commit into
Azure:devfrom
matt-richardson:fix/shutdown-listener-race-condition

Conversation

@matt-richardson

Copy link
Copy Markdown
Contributor

Summary

Fixes #2927

This PR fixes a race condition in the ShutdownListener constructor that causes a NullReferenceException when the shutdown token fires during initialization.

The Problem

The constructor was registering the cancellation callback before assigning _innerListener:

_shutdownToken = shutdownToken;
_shutdownRegistration = shutdownToken.Register(Cancel);  // Registers Cancel callback
_innerListener = innerListener;  // Assigned AFTER

If the shutdown token fired in the microseconds between registering the callback and assigning _innerListener, the Cancel() method would be invoked with _innerListener still null, resulting in a NullReferenceException at line 40.

The Fix

This PR swaps the order to assign _innerListener before registering the callback:

_shutdownToken = shutdownToken;
_innerListener = innerListener;  // Now assigned FIRST
_shutdownRegistration = shutdownToken.Register(Cancel);  // Safe to register

This ensures _innerListener is always initialized when Cancel() is invoked, eliminating the race condition.

Testing

  • ✅ Build verified successfully
  • This is a defensive fix for a rare production race condition

Impact

This is a critical fix for production stability. The race condition is rare but when it occurs, it causes the entire WebJob to crash during startup.

The ShutdownListener constructor was registering the Cancel callback
before assigning _innerListener. This created a race condition where
if the shutdownToken fired during or immediately after registration,
the Cancel() method would be called with _innerListener still null,
causing a NullReferenceException.

This fix assigns _innerListener before registering the callback,
ensuring it's always initialized when Cancel() is invoked.

Fixes Azure#2927
@matt-richardson

Copy link
Copy Markdown
Contributor Author

@jviau - could I trouble you to take a look at this PR as well?
(sorry to push my luck here)

@jviau

jviau commented Oct 15, 2025

Copy link
Copy Markdown
Contributor

/azp run webjobs-sdk.public

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@jviau jviau merged commit 94dd237 into Azure:dev Oct 15, 2025
4 checks passed
@matt-richardson

Copy link
Copy Markdown
Contributor Author

Thanks @jviau! Appreciate it.

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.

NRE in ShutdownListener

2 participants