Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/NodeApi/Interop/JSSynchronizationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,15 @@ public Task<T> RunAsync<T>(Func<Task<T>> asyncAction)
internal sealed class JSTsfnSynchronizationContext : JSSynchronizationContext
{
private readonly JSThreadSafeFunction _tsfn;
private bool _tsfnFinalized;

public JSTsfnSynchronizationContext()
{
_tsfn = new JSThreadSafeFunction(
maxQueueSize: 0,
initialThreadCount: 1,
asyncResourceName: (JSValue)nameof(JSSynchronizationContext));
asyncResourceName: (JSValue)nameof(JSSynchronizationContext),
finalize: _ => _tsfnFinalized = true);

// Unref TSFN to indicate that this TSFN is not preventing Node.JS shutdown.
_tsfn.Unref();
Expand All @@ -267,7 +269,15 @@ public override void Dispose()

// Destroy TSFN by releasing last thread use count.
// TSFN is deleted after this point and must not be used.
_tsfn.Release();
// During environment shutdown, Node.js finalizes the TSFN before the
// instance data finalizer disposes this context. Releasing it then
// deadlocks the JS thread on Node.js >= 24.14, where the release
// re-locks the TSFN mutex already held by the finalization path, and
// is a use-after-free on older versions.
if (!_tsfnFinalized)
{
_tsfn.Release();
}
}

/// <summary>
Expand Down