test: observe monitor waits without delays

This commit is contained in:
Erik 2026-08-18 12:56:25 +02:00
parent ea17bc8624
commit fa4bdfe89f
4 changed files with 127 additions and 35 deletions

View file

@ -20,20 +20,38 @@ public sealed class HostQuiescenceGateTests
Assert.True(callbackEntered.Wait(TimeSpan.FromSeconds(5)));
using var stopStarted = new ManualResetEventSlim(false);
Task stop = Task.Run(() =>
Exception? stopError = null;
var stopThread = new Thread(() =>
{
stopStarted.Set();
gate.StopAccepting();
});
Assert.True(stopStarted.Wait(TimeSpan.FromSeconds(5)));
await Task.Delay(50);
Assert.False(stop.IsCompleted);
try
{
gate.StopAccepting();
}
catch (Exception error)
{
stopError = error;
}
})
{
IsBackground = true,
Name = "HostQuiescenceGate external stop contract",
};
stopThread.Start();
bool stopStartedInTime = stopStarted.Wait(TimeSpan.FromSeconds(5));
bool stopBlockedOnCallback = stopStartedInTime && SpinWait.SpinUntil(
() => (stopThread.ThreadState & ThreadState.WaitSleepJoin) != 0,
TimeSpan.FromSeconds(5));
releaseCallback.Set();
await callback;
await stop;
bool stopJoined = stopThread.Join(TimeSpan.FromSeconds(5));
gate.Invoke(() => calls++);
Assert.True(stopStartedInTime, "the dedicated stop thread did not start");
Assert.True(stopBlockedOnCallback, "stop never waited for the admitted callback");
Assert.True(stopJoined, "stop did not finish after the callback returned");
Assert.Null(stopError);
Assert.Equal(1, calls);
Assert.False(gate.IsAccepting);
}

View file

@ -270,19 +270,30 @@ public sealed class SilkWindowCallbackBindingTests
Task<Exception> first = Task.Run(() => Record.Exception(binding.Dispose));
Assert.True(surface.RemoveEntered.Wait(TimeSpan.FromSeconds(5)));
using var secondEntered = new ManualResetEventSlim(false);
Task<Exception> second = Task.Run(() =>
Exception? secondError = null;
var secondThread = new Thread(() =>
{
secondEntered.Set();
return Record.Exception(binding.Dispose);
});
Assert.True(secondEntered.Wait(TimeSpan.FromSeconds(5)));
await Task.Delay(50);
Assert.False(second.IsCompleted);
secondError = Record.Exception(binding.Dispose);
})
{
IsBackground = true,
Name = "Silk callback binding concurrent dispose contract",
};
secondThread.Start();
bool secondStartedInTime = secondEntered.Wait(TimeSpan.FromSeconds(5));
bool secondBlockedOnDetach = secondStartedInTime && SpinWait.SpinUntil(
() => (secondThread.ThreadState & ThreadState.WaitSleepJoin) != 0,
TimeSpan.FromSeconds(5));
surface.ContinueRemove.Set();
Assert.Null(await first);
Assert.Null(await second);
bool secondJoined = secondThread.Join(TimeSpan.FromSeconds(5));
Assert.True(secondStartedInTime, "the second dispose thread did not start");
Assert.True(secondBlockedOnDetach, "the second dispose never waited for physical detach");
Assert.True(secondJoined, "the second dispose did not finish after detach completed");
Assert.Null(secondError);
Assert.True(binding.IsDetached);
}
@ -306,19 +317,30 @@ public sealed class SilkWindowCallbackBindingTests
Task<Exception> first = Task.Run(() => Record.Exception(binding.Dispose));
Assert.True(surface.RemoveEntered.Wait(TimeSpan.FromSeconds(5)));
using var secondEntered = new ManualResetEventSlim(false);
Task<Exception> second = Task.Run(() =>
Exception? secondError = null;
var secondThread = new Thread(() =>
{
secondEntered.Set();
return Record.Exception(binding.Dispose);
});
Assert.True(secondEntered.Wait(TimeSpan.FromSeconds(5)));
await Task.Delay(50);
Assert.False(second.IsCompleted);
secondError = Record.Exception(binding.Dispose);
})
{
IsBackground = true,
Name = "Silk callback binding detach failure contract",
};
secondThread.Start();
bool secondStartedInTime = secondEntered.Wait(TimeSpan.FromSeconds(5));
bool secondBlockedOnDetach = secondStartedInTime && SpinWait.SpinUntil(
() => (secondThread.ThreadState & ThreadState.WaitSleepJoin) != 0,
TimeSpan.FromSeconds(5));
surface.ContinueRemove.Set();
Assert.IsType<AggregateException>(await first);
Assert.IsType<AggregateException>(await second);
bool secondJoined = secondThread.Join(TimeSpan.FromSeconds(5));
Assert.True(secondStartedInTime, "the second dispose thread did not start");
Assert.True(secondBlockedOnDetach, "the second dispose never waited for physical detach");
Assert.True(secondJoined, "the second dispose did not finish after detach failed");
Assert.IsType<AggregateException>(secondError);
Assert.False(binding.IsDetached);
surface.PersistentRemoveFailures.Clear();