I ran into what appears to be a bug with Monitor.Wait(object, TimeSpan)
, when timeout is 0, it incorrectly returns true when it should return false. My test passed in every other runtime except IL2CPP (Mono, .Net 6, .Net Framework 4.8). It only fails when timeout is 0. It works correctly when timeout is -1 (infinite), or positive.
internal sealed partial class PromiseSynchronousWaiter : HandleablePromiseBase
{
private PromiseSynchronousWaiter() { }
internal static bool TryWaitForCompletion(PromiseRefBase promise, short promiseId, TimeSpan timeout)
{
var waiter = ObjectPool.TryTake<PromiseSynchronousWaiter>()
?? new PromiseSynchronousWaiter();
lock (waiter)
{
waiter._didWaitSuccessfully = false;
waiter._didWait = false;
waiter._isHookingUp = true;
UnityEngine.Debug.Log("TryWaitForCompletion, hook up");
promise.HookupExistingWaiter(promiseId, waiter);
// Check the flag in case Handle is invoked synchronously.
UnityEngine.Debug.Log($"TryWaitForCompletion, waiter._isHookingUp: {waiter._isHookingUp}");
if (waiter._isHookingUp)
{
waiter._isHookingUp = false;
waiter._didWaitSuccessfully = Monitor.Wait(waiter, timeout);
UnityEngine.Debug.Log($"TryWaitForCompletion, waiter._didWaitSuccessfully: {waiter._didWaitSuccessfully}");
Thread.MemoryBarrier(); // Make sure _didWait is written last.
waiter._didWait = true;
return waiter._didWaitSuccessfully;
}
}
ObjectPool.MaybeRepool(waiter);
return true;
}
internal override void Handle(PromiseRefBase handler)
{
lock (this)
{
UnityEngine.Debug.Log($"Handle, _isHookingUp: {_isHookingUp}");
if (_isHookingUp)
{
_isHookingUp = false;
return;
}
// Wake the other thread.
Monitor.Pulse(this);
}
// Wait until we're sure the other thread has continued.
var spinner = new SpinWait();
while (!_didWait)
{
spinner.SpinOnce();
}
UnityEngine.Debug.Log($"Handle, _didWaitSuccessfully: {_didWaitSuccessfully}");
// If the timeout expired before completion, we dispose the handler here. Otherwise, the original caller will dispose it.
if (!_didWaitSuccessfully)
{
handler.MaybeDispose();
}
ObjectPool.MaybeRepool(this);
}
}
In my test, I force the other thread to wait 1/2 second before invoking Handle
, so Monitor.Wait
should 100% return false, not true.
Output:
I tested this from Unity 2018 all the way up to 2022.1.17, and got the same results every time. Mono works correctly on every version.