[corlib] Fix race condition between RegisteredWaitHandle Wait and Unregister methods
In the following case we would have a race between Wait and Unregister methods:
- T1 calls ThreadPool.RegisterWaitForSingleObject to wait on waitHandle
- registeredWaitHandle is constructed to wait on waitHandle
- registeredWaitHandle.Wait method is enqueued on the ThreadPool
- T1 calls registeredWaitHandle.Unregister
- T1 calls waitHandle.Close (happens in Process.StopWatchingForExit)
- TTP (the threadpool thread) calls registeredWaitHandle.Wait
- TTP calls WaitHandle.WaitAny (waitHandle, ...)
The last step try to access the waitHandle that has been previously closed, leading to a _wapi_handle_ref / _wapi_handle_unref_full error message.
The fix is to ensure that the RegisteredWaitHandle keeps the WaitHandle alive. To achieve this, we simply use the SafeWaitHandle inside the WaitHandle by: increasing its ref count when constructing the RegisteredWaitHandle, and decrementing it when unregistering it.
We also increase the count when using it in Wait, so if we execute Unregister and Wait concurrently, we guarantee that the WaitHandle is still valid in Wait, or get an exception if not.