Mono.Debugger.Soft has low frequency hangs in InspectThreadSuspenedOnWaitOne test.
This method launch the debuggee (dtest-app.exe), sets a breakpoint on a method just
doing an infinite OS wait, validate that the breakpoint gets hit and resumes debuggee.
After resume from breakpoint debuggee will hit the infinite OS wait and the debugger test
will suspend and shutdown the process.
In order for mono debugger to handle the shutdown request it will suspend all managed threads
using mono_thread_suspend_all_other_threads. This method uses a pooling schema until all threads
have reported that they are suspended. Since one of the threads are doing an infinite OS wait we
entered the land of APC (Asynchron Procedure Calls) used on Windows to alert waitable threads.
The reason for the hang is the fact that the OS won’t return from the wait until all queued APC’s
have been executed. Since the loop sending async suspend requests to the
threads mono_thread_suspend_all_other_threads can run faster (including suspending/resuming the
thread that need to consume APC’s in the process) then the dequeue and execute of the queued APC’s, the
wait won’t break, meaning that the thread won’t be able to set the mono suspend state causing the
mono_thread_suspend_all_other_threads to run forever. This is highly timing dependent and will only reproduce
sporadic, but I was able to isolate and do a solid repro locally while working on the fix.
The fix makes sure we don’t post a new suspend APC if there is already one in flight with a pending unhandled
interruption. This will make sure we won’t flood the APC queue on Windows preventing the thread from suspending.
#include <mono/utils/hazard-pointer.h>
#include <mono/utils/w32api.h>
#include <mono/utils/unlocked.h>
+#include <mono/utils/mono-os-wait.h>
#ifndef HOST_WIN32
#include <pthread.h>
mono_gc_finalize_notify ();
/* g_print ("Waiting for pending finalizers....\n"); */
MONO_ENTER_GC_SAFE;
- WaitForSingleObjectEx (pending_done_event, INFINITE, TRUE);
+ mono_win32_wait_for_single_object_ex (pending_done_event, INFINITE, TRUE);
MONO_EXIT_GC_SAFE;
/* g_print ("Done pending....\n"); */
#else
#include <mono/utils/mono-time.h>
#include <mono/utils/atomic.h>
#include <mono/utils/w32api.h>
+#include <mono/utils/mono-os-wait.h>
/*
* Pull the list of opcodes
*/
MONO_ENTER_GC_SAFE;
#ifdef HOST_WIN32
- ret = mono_w32handle_convert_wait_ret (WaitForSingleObjectEx (event, ms, TRUE), 1);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_single_object_ex (event, ms, TRUE), 1);
#else
ret = mono_w32handle_wait_one (event, ms, TRUE);
#endif /* HOST_WIN32 */
*/
MONO_ENTER_GC_SAFE;
#ifdef HOST_WIN32
- ret = mono_w32handle_convert_wait_ret (WaitForSingleObjectEx (event, 0, FALSE), 1);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_single_object_ex (event, 0, FALSE), 1);
#else
ret = mono_w32handle_wait_one (event, 0, FALSE);
#endif /* HOST_WIN32 */
#include <mono/utils/mono-threads.h>
#include <mono/utils/mono-time.h>
#include <mono/utils/refcount.h>
+#include <mono/utils/mono-os-wait.h>
typedef struct {
MonoDomain *domain;
mono_monitor_exit ((MonoObject*) ares);
MONO_ENTER_GC_SAFE;
#ifdef HOST_WIN32
- WaitForSingleObjectEx (wait_event, INFINITE, TRUE);
+ mono_win32_wait_for_single_object_ex (wait_event, INFINITE, TRUE);
#else
mono_w32handle_wait_one (wait_event, MONO_INFINITE_WAIT, TRUE);
#endif
#include <mono/metadata/abi-details.h>
#include <mono/metadata/w32error.h>
#include <mono/utils/w32api.h>
+#include <mono/utils/mono-os-wait.h>
#ifdef HAVE_SIGNAL_H
#include <signal.h>
MONO_ENTER_GC_SAFE;
#ifdef HOST_WIN32
if (numhandles != 1)
- ret = mono_w32handle_convert_wait_ret (WaitForMultipleObjectsEx (numhandles, handles, waitall, timeoutLeft, TRUE), numhandles);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_multiple_objects_ex(numhandles, handles, waitall, timeoutLeft, TRUE), numhandles);
else
- ret = mono_w32handle_convert_wait_ret (WaitForSingleObjectEx (handles [0], timeoutLeft, TRUE), 1);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_wait_for_single_object_ex (handles [0], timeoutLeft, TRUE), 1);
#else
/* mono_w32handle_wait_multiple optimizes the case for numhandles == 1 */
ret = mono_w32handle_wait_multiple (handles, numhandles, waitall, timeoutLeft, TRUE);
MONO_ENTER_GC_SAFE;
#ifdef HOST_WIN32
- ret = mono_w32handle_convert_wait_ret (SignalObjectAndWait (toSignal, toWait, ms, TRUE), 1);
+ ret = mono_w32handle_convert_wait_ret (mono_win32_signal_object_and_wait (toSignal, toWait, ms, TRUE), 1);
#else
ret = mono_w32handle_signal_and_wait (toSignal, toWait, ms, TRUE);
#endif
}
/* this will consume pending APC calls */
-#ifdef HOST_WIN32
- WaitForSingleObjectEx (GetCurrentThread(), 0, TRUE);
+#ifdef USE_WINDOWS_BACKEND
+ mono_win32_wait_for_single_object_ex (GetCurrentThread (), 0, TRUE);
#endif
+
/* Clear the interrupted flag of the thread so it can wait again */
mono_thread_info_clear_self_interrupt ();
/* this will awake the thread if it is in WaitForSingleObject
or similar */
- /* Our implementation of this function ignores the func argument */
-#ifdef HOST_WIN32
- QueueUserAPC ((PAPCFUNC)dummy_apc, thread->native_handle, (ULONG_PTR)NULL);
+#ifdef USE_WINDOWS_BACKEND
+ mono_win32_interrupt_wait (thread->thread_info, thread->native_handle, (DWORD)thread->tid);
#else
mono_thread_info_self_interrupt ();
#endif
#include "w32socket-internals.h"
#include "utils/w32api.h"
+#include "utils/mono-os-wait.h"
#define LOGDEBUG(...)
WSAEVENT event = WSACreateEvent ();
if (event != WSA_INVALID_EVENT) {
if (WSAEventSelect (sock, event, (1 << event_bit) | FD_CLOSE) != SOCKET_ERROR) {
- LOGDEBUG (g_message ("%06d - Calling WSAWaitForMultipleEvents () on socket %d", GetCurrentThreadId (), sock));
- DWORD ret = WSAWaitForMultipleEvents (1, &event, TRUE, timeout, TRUE);
+ LOGDEBUG (g_message ("%06d - Calling mono_win32_wsa_wait_for_multiple_events () on socket %d", GetCurrentThreadId (), sock));
+ DWORD ret = mono_win32_wsa_wait_for_multiple_events (1, &event, TRUE, timeout, TRUE);
if (ret == WSA_WAIT_IO_COMPLETION) {
- LOGDEBUG (g_message ("%06d - WSAWaitForMultipleEvents () returned WSA_WAIT_IO_COMPLETION for socket %d", GetCurrentThreadId (), sock));
+ LOGDEBUG (g_message ("%06d - mono_win32_wsa_wait_for_multiple_events () returned WSA_WAIT_IO_COMPLETION for socket %d", GetCurrentThreadId (), sock));
error = WSAEINTR;
} else if (ret == WSA_WAIT_TIMEOUT) {
error = WSAETIMEDOUT;
if (error == WSA_IO_PENDING) {
error = 0;
// NOTE: .NET's Socket.SendFile() doesn't honor the Socket's SendTimeout so we shouldn't either
- DWORD ret = WaitForSingleObjectEx (overlapped.hEvent, INFINITE, TRUE);
+ DWORD ret = mono_win32_wait_for_single_object_ex (overlapped.hEvent, INFINITE, TRUE);
if (ret == WAIT_IO_COMPLETION) {
- LOGDEBUG (g_message ("%06d - WaitForSingleObjectEx () returned WSA_WAIT_IO_COMPLETION for socket %d", GetCurrentThreadId (), hSocket));
+ LOGDEBUG (g_message ("%06d - mono_win32_wait_for_single_object_ex () returned WSA_WAIT_IO_COMPLETION for socket %d", GetCurrentThreadId (), hSocket));
error = WSAEINTR;
} else if (ret == WAIT_TIMEOUT) {
error = WSAETIMEDOUT;
if HOST_WIN32
win32_sources = \
- os-event-win32.c
+ os-event-win32.c \
+ mono-os-wait-win32.c
platform_sources = $(win32_sources)
else
mono-mmap-windows-internals.h \
mono-os-mutex.h \
mono-os-mutex.c \
+ mono-os-wait.h \
mono-coop-mutex.h \
mono-once.h \
mono-lazy-init.h \
#elif !defined(HOST_WIN32) && defined(HAVE_SEMAPHORE_H)
#include <semaphore.h>
#else
-#include <winsock2.h>
-#include <windows.h>
+#include <mono/utils/mono-os-wait.h>
#endif
#define MONO_HAS_SEMAPHORES 1
BOOL res;
retry:
- res = WaitForSingleObjectEx (*sem, timeout_ms, flags & MONO_SEM_FLAGS_ALERTABLE);
+ res = mono_win32_wait_for_single_object_ex (*sem, timeout_ms, flags & MONO_SEM_FLAGS_ALERTABLE);
if (G_UNLIKELY (res != WAIT_OBJECT_0 && res != WAIT_IO_COMPLETION && res != WAIT_TIMEOUT))
- g_error ("%s: WaitForSingleObjectEx failed with error %d", __func__, GetLastError ());
+ g_error ("%s: mono_win32_wait_for_single_object_ex failed with error %d", __func__, GetLastError ());
if (res == WAIT_IO_COMPLETION && !(flags & MONO_SEM_FLAGS_ALERTABLE))
goto retry;
--- /dev/null
+#include <mono/utils/mono-os-wait.h>
+#include <mono/utils/mono-threads.h>
+#include <mono/utils/mono-threads-debug.h>
+
+#define THREAD_WAIT_INFO_CLEARED 0
+#define THREAD_WAIT_INFO_ALERTABLE_WAIT_SLOT 1
+#define THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT 2
+#define THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT 4
+
+static inline void
+reqeust_interrupt (gpointer thread_info, HANDLE native_thread_handle, gint32 pending_apc_slot, PAPCFUNC apc_callback, DWORD tid)
+{
+ /*
+ * On Windows platforms the async interrupt/abort requests might need to process
+ * APC on target thread before the thread can return back from OS wait calls
+ * and complete the mono interrupt/abort request. In such cases, just keep on
+ * queuing APC over again again will flood the APC queue preventing the target thread
+ * to return from it's alertable OS wait call. This check makes sure not to issue an APC
+ * as long as there is a pending requests in requested APC slot for the targeted thread.
+ * NOTE, this code will executed regardless if thread is currently in an alertable wait or not.
+ * This is done to prevent races between interrupt/abort occurring just before thread enters an
+ * alertable wait. Threads entering waits already need to handle WAIT_IO_COMPLETION scenarios and
+ * if that happens due to a previous pending interrupt/abort APC, the calling thread should already
+ * have logic to restart the alertable wait operation.
+ */
+ MonoThreadInfo *info = (MonoThreadInfo *)thread_info;
+ gboolean queue_apc = FALSE;
+
+ while (!queue_apc) {
+ gint32 old_wait_info = InterlockedRead (&info->thread_wait_info);
+ if (old_wait_info & pending_apc_slot)
+ break;
+
+ gint32 new_wait_info = old_wait_info | pending_apc_slot;
+ if (InterlockedCompareExchange (&info->thread_wait_info, new_wait_info, old_wait_info) == old_wait_info) {
+ queue_apc = TRUE;
+ }
+ }
+
+ if (queue_apc == TRUE) {
+ THREADS_INTERRUPT_DEBUG ("%06d - Interrupting/Aborting syscall in thread %06d", GetCurrentThreadId (), tid);
+ QueueUserAPC (apc_callback, native_thread_handle, (ULONG_PTR)NULL);
+ }
+}
+
+static void CALLBACK
+interrupt_apc (ULONG_PTR param)
+{
+ THREADS_INTERRUPT_DEBUG ("%06d - interrupt_apc () called", GetCurrentThreadId ());
+}
+
+void
+mono_win32_interrupt_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid)
+{
+ reqeust_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT, interrupt_apc, tid);
+}
+
+static void CALLBACK
+abort_apc (ULONG_PTR param)
+{
+ THREADS_INTERRUPT_DEBUG ("%06d - abort_apc () called", GetCurrentThreadId ());
+}
+
+void
+mono_win32_abort_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid)
+{
+ reqeust_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT, abort_apc, tid);
+}
+
+static inline void
+enter_alertable_wait (MonoThreadInfo *info)
+{
+ // Clear any previous flags. Set alertable wait flag.
+ InterlockedExchange (&info->thread_wait_info, THREAD_WAIT_INFO_ALERTABLE_WAIT_SLOT);
+}
+
+static inline void
+leave_alertable_wait (MonoThreadInfo *info)
+{
+ // Clear any previous flags. Thread is exiting alertable wait state, and info around pending interrupt/abort APC's
+ // can now be discarded as well, thread is out of wait operation and can proceed it's execution.
+ InterlockedExchange (&info->thread_wait_info, THREAD_WAIT_INFO_CLEARED);
+}
+
+DWORD
+mono_win32_sleep_ex (DWORD timeout, BOOL alertable)
+{
+ DWORD result = WAIT_FAILED;
+ MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+
+ if (alertable && info) {
+ enter_alertable_wait (info);
+ }
+
+ result = SleepEx (timeout, alertable);
+
+ // NOTE, leave_alertable_wait should not affect GetLastError but
+ // if changed, last error need to be preserved and reset before returning.
+ if (alertable && info) {
+ leave_alertable_wait (info);
+ }
+
+ return result;
+}
+
+DWORD
+mono_win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertable)
+{
+ DWORD result = WAIT_FAILED;
+ MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+
+ if (alertable && info) {
+ enter_alertable_wait (info);
+ }
+
+ result = WaitForSingleObjectEx (handle, timeout, alertable);
+
+ // NOTE, leave_alertable_wait should not affect GetLastError but
+ // if changed, last error need to be preserved and reset before returning.
+ if (alertable && info) {
+ leave_alertable_wait (info);
+ }
+
+ return result;
+}
+
+DWORD
+mono_win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOOL waitAll, DWORD timeout, BOOL alertable)
+{
+ DWORD result = WAIT_FAILED;
+ MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+
+ if (alertable && info) {
+ enter_alertable_wait (info);
+ }
+
+ result = WaitForMultipleObjectsEx (count, handles, waitAll, timeout, alertable);
+
+ // NOTE, leave_alertable_wait should not affect GetLastError but
+ // if changed, last error need to be preserved and reset before returning.
+ if (alertable && info) {
+ leave_alertable_wait (info);
+ }
+
+ return result;
+}
+
+DWORD
+mono_win32_signal_object_and_wait (HANDLE toSignal, HANDLE toWait, DWORD timeout, BOOL alertable)
+{
+ DWORD result = WAIT_FAILED;
+ MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+
+ if (alertable && info) {
+ enter_alertable_wait (info);
+ }
+
+ result = SignalObjectAndWait (toSignal, towlower, timeout, alertable);
+
+ // NOTE, leave_alertable_wait should not affect GetLastError but
+ // if changed, last error need to be preserved and reset before returning.
+ if (alertable && info) {
+ leave_alertable_wait (info);
+ }
+
+ return result;
+}
+
+DWORD
+mono_win32_msg_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, DWORD timeout, DWORD wakeMask, DWORD flags)
+{
+ DWORD result = WAIT_FAILED;
+ MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+ BOOL alertable = flags & MWMO_ALERTABLE;
+
+ if (alertable && info) {
+ enter_alertable_wait (info);
+ }
+
+ result = MsgWaitForMultipleObjectsEx (count, handles, timeout, wakeMask, flags);
+
+ // NOTE, leave_alertable_wait should not affect GetLastError but
+ // if changed, last error need to be preserved and reset before returning.
+ if (alertable && info) {
+ leave_alertable_wait (info);
+ }
+
+ return result;
+}
+
+DWORD
+mono_win32_wsa_wait_for_multiple_events (DWORD count, const WSAEVENT FAR *handles, BOOL waitAll, DWORD timeout, BOOL alertable)
+{
+ DWORD result = WAIT_FAILED;
+ MonoThreadInfo *info = mono_thread_info_current_unchecked ();
+
+ if (alertable && info) {
+ enter_alertable_wait (info);
+ }
+
+ result = WSAWaitForMultipleEvents (count, handles, waitAll, timeout, alertable);
+
+ // NOTE, leave_alertable_wait should not affect GetLastError but
+ // if changed, last error need to be preserved and reset before returning.
+ if (alertable && info) {
+ leave_alertable_wait (info);
+ }
+
+ return result;
+}
--- /dev/null
+#ifndef _MONO_UTILS_OS_WAIT_H_
+#define _MONO_UTILS_OS_WAIT_H_
+
+#include <config.h>
+#ifdef HOST_WIN32
+
+#include <winsock2.h>
+#include <windows.h>
+
+DWORD
+mono_win32_sleep_ex (DWORD timeout, BOOL alertable);
+
+DWORD
+mono_win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertable);
+
+DWORD
+mono_win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOOL waitAll, DWORD timeout, BOOL alertable);
+
+DWORD
+mono_win32_signal_object_and_wait (HANDLE toSignal, HANDLE toWait, DWORD timeout, BOOL alertable);
+
+DWORD
+mono_win32_msg_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, DWORD timeout, DWORD wakeMask, DWORD flags);
+
+DWORD
+mono_win32_wsa_wait_for_multiple_events (DWORD count, const WSAEVENT FAR *handles, BOOL waitAll, DWORD timeout, BOOL alertable);
+
+void
+mono_win32_interrupt_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid);
+
+void
+mono_win32_abort_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid);
+
+#endif
+
+#endif /* _MONO_UTILS_OS_WAIT_H_ */
#include <mono/utils/mono-compiler.h>
#include <mono/utils/mono-threads-debug.h>
+#include <mono/utils/mono-os-wait.h>
#include <limits.h>
-
void
mono_threads_suspend_init (void)
{
}
-static void CALLBACK
-interrupt_apc (ULONG_PTR param)
-{
-}
-
gboolean
mono_threads_suspend_begin_async_suspend (MonoThreadInfo *info, gboolean interrupt_kernel)
{
info->suspend_can_continue = mono_threads_get_runtime_callbacks ()->thread_state_init_from_handle (&info->thread_saved_state [ASYNC_SUSPEND_STATE_INDEX], info);
THREADS_SUSPEND_DEBUG ("thread state %p -> %d\n", (void*)id, res);
if (info->suspend_can_continue) {
- //FIXME do we need to QueueUserAPC on this case?
if (interrupt_kernel)
- QueueUserAPC ((PAPCFUNC)interrupt_apc, handle, (ULONG_PTR)NULL);
+ mono_win32_interrupt_wait (info, handle, id);
} else {
THREADS_SUSPEND_DEBUG ("FAILSAFE RESUME/2 %p -> %d\n", (void*)info->native_handle, 0);
}
return info->suspend_can_continue;
}
-static void CALLBACK
-abort_apc (ULONG_PTR param)
-{
- THREADS_INTERRUPT_DEBUG ("%06d - abort_apc () called", GetCurrentThreadId ());
-}
+
void
mono_threads_suspend_abort_syscall (MonoThreadInfo *info)
handle = OpenThread (THREAD_ALL_ACCESS, FALSE, id);
g_assert (handle);
- THREADS_INTERRUPT_DEBUG ("%06d - Aborting syscall in thread %06d", GetCurrentThreadId (), id);
- QueueUserAPC ((PAPCFUNC)abort_apc, handle, (ULONG_PTR)NULL);
+ mono_win32_abort_wait (info, handle, id);
CloseHandle (handle);
}
* handled a sampling signal before sending another one.
*/
gint32 profiler_signal_ack;
+
+#ifdef USE_WINDOWS_BACKEND
+ gint32 thread_wait_info;
+#endif
+
} MonoThreadInfo;
typedef struct {
#include <winbase.h>
#include "atomic.h"
+#include "mono-os-wait.h"
void
mono_os_event_init (MonoOSEvent *event, gboolean initial)
g_assert (event);
g_assert (event->handle);
- res = WaitForSingleObjectEx (event->handle, timeout, alertable);
+ res = mono_win32_wait_for_single_object_ex (event->handle, timeout, alertable);
if (res == WAIT_OBJECT_0)
return MONO_OS_EVENT_WAIT_RET_SUCCESS_0;
else if (res == WAIT_IO_COMPLETION)
else if (res == WAIT_TIMEOUT)
return MONO_OS_EVENT_WAIT_RET_TIMEOUT;
else if (res == WAIT_FAILED)
- g_error ("%s: WaitForSingleObjectEx failed with error %d", __func__, GetLastError ());
+ g_error ("%s: mono_thread_win32_wait_one_handle failed with error %d", __func__, GetLastError ());
else
g_error ("%s: unknown res value %d", __func__, res);
}
handles [i] = events [i]->handle;
}
- res = WaitForMultipleObjectsEx (nevents, handles, waitall, timeout, alertable);
+ res = mono_win32_wait_for_multiple_objects_ex ((DWORD)nevents, handles, waitall, timeout, alertable);
if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + MONO_OS_EVENT_WAIT_MAXIMUM_OBJECTS)
return MONO_OS_EVENT_WAIT_RET_SUCCESS_0 + (res - WAIT_OBJECT_0);
else if (res == WAIT_IO_COMPLETION)
else if (res == WAIT_TIMEOUT)
return MONO_OS_EVENT_WAIT_RET_TIMEOUT;
else if (res == WAIT_FAILED)
- g_error ("%s: WaitForSingleObjectEx failed with error %d", __func__, GetLastError ());
+ g_error ("%s: mono_thread_win32_wait_multiple_handle failed with error %d", __func__, GetLastError ());
else
g_error ("%s: unknown res value %d", __func__, res);
}
-<?xml version="1.0" encoding="utf-8"?>\r
+<?xml version="1.0" encoding="utf-8"?>\r
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\r
<ItemGroup Label="ProjectConfigurations">\r
<ProjectConfiguration Include="Debug|Win32">\r
<ClCompile Include="..\mono\utils\mono-mmap-windows.c" />\r
<ClCompile Include="..\mono\utils\mono-mmap.c" />\r
<ClCompile Include="..\mono\utils\mono-networkinterfaces.c" />\r
+ <ClCompile Include="..\mono\utils\mono-os-wait-win32.c" />\r
<ClCompile Include="..\mono\utils\mono-proclib-windows.c" />\r
<ClCompile Include="..\mono\utils\mono-rand-windows.c" />\r
<ClCompile Include="..\mono\utils\mono-rand.c" />\r
<ClInclude Include="..\mono\utils\mono-once.h" />\r
<ClInclude Include="..\mono\utils\mono-os-mutex.h" />\r
<ClInclude Include="..\mono\utils\mono-os-semaphore.h" />\r
+ <ClInclude Include="..\mono\utils\mono-os-wait.h" />\r
<ClInclude Include="..\mono\utils\mono-path.h" />\r
<ClInclude Include="..\mono\utils\mono-poll.h" />\r
<ClInclude Include="..\mono\utils\mono-proclib-windows-internals.h" />\r
<ClInclude Include="..\mono\utils\strenc.h" />\r
<ClInclude Include="..\mono\utils\valgrind.h" />\r
<ClInclude Include="..\mono\utils\atomic.h" />\r
- <ClInclude Include="..\mono\utils\unlocked.h" />
+ <ClInclude Include="..\mono\utils\unlocked.h" />\r
<ClInclude Include="..\mono\utils\mono-hwcap.h" />\r
<ClInclude Include="..\mono\utils\mono-hwcap-x86.h" />\r
<ClInclude Include="..\mono\utils\bsearch.h" />\r
<ClCompile Include="..\mono\utils\mono-os-mutex.c">\r
<Filter>Source Files</Filter>\r
</ClCompile>\r
+ <ClCompile Include="..\mono\utils\mono-os-wait-win32.c">\r
+ <Filter>Source Files</Filter>\r
+ </ClCompile>\r
</ItemGroup>\r
<ItemGroup>\r
<ClInclude Include="..\mono\utils\atomic.h">\r
<ClInclude Include="..\mono\utils\os-event.h">\r
<Filter>Header Files</Filter>\r
</ClInclude>\r
- <ClInclude Include="..\mono\utils\unlocked.h">
- <Filter>Header Files</Filter>
- </ClInclude>
+ <ClInclude Include="..\mono\utils\unlocked.h">\r
+ <Filter>Header Files</Filter>\r
+ </ClInclude>\r
+ <ClInclude Include="..\mono\utils\mono-os-wait.h">\r
+ <Filter>Header Files</Filter>\r
+ </ClInclude>\r
</ItemGroup>\r
<ItemGroup>\r
<Filter Include="Header Files">\r