Skip to content

[Mono] Reentrant Monitor.Wait unexpected behavior #126138

Closed

Description

@eduardo-vp

There's a current bug that can happen when using synchronization contexts that allow message pumping:

  • The main thread calls Monitor.Wait on lock A.
  • SynchronizationContext.Wait pumps a message and the main thread calls Monitor.Wait on lock B.
  • A background thread calls Monitor.Pulse on lock A but lock B steals the signal.

public static void ReentrantWaitFromSyncContextTest()
{
// Since we set the SynchronizationContext for the current thread, we run
// this test in a background thread to avoid affecting other tests.
ThreadTestHelpers.RunTestInBackgroundThread(() =>
{
object lockA = new();
object lockB = new();
bool innerWaitResult = true; // should become false (lockB is never pulsed)
var pumpCtx = new ReentrantWaitSyncContext(() =>
{
lock (lockB)
{
innerWaitResult = Monitor.Wait(lockB, 500);
}
});
SynchronizationContext.SetSynchronizationContext(pumpCtx);
var t = new Thread(() =>
{
Thread.Sleep(100);
lock (lockA) { Monitor.Pulse(lockA); }
}) { IsBackground = true };
t.Start();
bool outerResult;
var sw = System.Diagnostics.Stopwatch.StartNew();
lock (lockA)
{
outerResult = Monitor.Wait(lockA, FailTimeoutMilliseconds);
}
long elapsed = sw.ElapsedMilliseconds;
Assert.True(outerResult, "Outer Monitor.Wait should have been pulsed");
Assert.True(elapsed < FailTimeoutMilliseconds / 2,
$"Outer Monitor.Wait took {elapsed}ms — signal was likely stolen by inner wait");
Assert.False(innerWaitResult,
"Inner Monitor.Wait(lockB) should return false (lockB was never pulsed)");
t.Join(FailTimeoutMilliseconds);
});
}

Activity

  1. dotnet-policy-service commented on Mar 26, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @agocke, @VSadov
    See info in area-owners.md if you want to be subscribed.

  2. added
    disabled-testThe test is disabled in source code against the issue
    on Mar 26, 2026
  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Apr 1, 2026
  4. JulieLeeMSFT commented on Jul 27, 2026

    @JulieLeeMSFT
    Member

    @eduardo-vp, do you plan to fix this issue in .NET 11?
    cc @VSadov.

  5. eduardo-vp commented on Jul 29, 2026

    @eduardo-vp
    MemberAuthor

    I looked more into this and actually the initial issue #123173 was possible because after Monitor was moved to managed code in #118371, Monitor.Wait now blocks on an AutoResetEvent via WaitOneNoCheck which ends up forwarding the wait to SynchronizationContext.Wait when the context has called SetWaitNotificationRequired. On Mono Monitor.Wait is still native, doesn't go through that override so there's no room for the bug to happen.

    This is more of a parity gap, Mono doesn't honor SynchronizationContext wait notification which matches CoreCLR in .NET 10 and earlier. Not sure if this is something we actually need to address but for now I think the test just shouldn't run on Mono since it lacks the behavior necessary to repro the bug.

  6. added this to the 12.0.0 milestone on Aug 24, 2026
  7. JulieLeeMSFT commented on Aug 24, 2026

    @JulieLeeMSFT
    Member

    Set to .NET 12.

  8. added a commit that references this issue on Aug 24, 2026
    213ba9b
  9. locked and limited conversation to collaborators on Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions