Skip to content

[Mono] Reentrant Monitor.Wait unexpected behavior #126138

@eduardo-vp

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);
});
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions