+/**
+* \file
+* Win32 OS wait wrappers and interrupt/abort APC handling.
+*
+* Author:
+* Johan Lorensson (lateralusx.github@gmail.com)
+*
+* Licensed under the MIT license. See LICENSE file in the project root for full license information.
+*/
+
#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
+enum ThreadWaitInfo {
+ THREAD_WAIT_INFO_CLEARED = 0,
+ THREAD_WAIT_INFO_ALERTABLE_WAIT_SLOT = 1 << 0,
+ THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT = 1 << 1,
+ THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT = 1 << 2
+};
static inline void
-reqeust_interrupt (gpointer thread_info, HANDLE native_thread_handle, gint32 pending_apc_slot, PAPCFUNC apc_callback, DWORD tid)
+request_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.
+ * On Windows platforms, an async interrupt/abort request queues an APC
+ * that needs to be processed by target thread before it can return from an
+ * alertable OS wait call and complete the mono interrupt/abort request.
+ * Uncontrolled queuing of APC's could flood the APC queue preventing the target thread
+ * to return from its alertable OS wait call, blocking the interrupt/abort requests to complete
+ * This check makes sure that only one APC per type gets queued, preventing potential flooding
+ * of the APC queue. NOTE, this code will execute regardless if targeted thread is currently in
+ * an alertable wait or not. This is done to prevent races between interrupt/abort requests and
+ * alertable wait calls. Threads already in an alertable wait should handle WAIT_IO_COMPLETION
+ * return scenarios and restart the alertable wait operation if needed or take other actions
+ * (like service the interrupt/abort request).
*/
MonoThreadInfo *info = (MonoThreadInfo *)thread_info;
- gboolean queue_apc = FALSE;
+ gint32 old_wait_info, new_wait_info;
- while (!queue_apc) {
- gint32 old_wait_info = InterlockedRead (&info->thread_wait_info);
+ do {
+ old_wait_info = InterlockedRead (&info->thread_wait_info);
if (old_wait_info & pending_apc_slot)
- break;
+ return;
- 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;
- }
- }
+ new_wait_info = old_wait_info | pending_apc_slot;
+ } while (InterlockedCompareExchange (&info->thread_wait_info, new_wait_info, old_wait_info) != old_wait_info);
- 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);
- }
+ THREADS_INTERRUPT_DEBUG ("%06d - Interrupting/Aborting syscall in thread %06d", GetCurrentThreadId (), tid);
+ QueueUserAPC (apc_callback, native_thread_handle, (ULONG_PTR)NULL);
}
static void CALLBACK
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);
+ request_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT, interrupt_apc, tid);
}
static void CALLBACK
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);
+ request_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT, abort_apc, tid);
}
static inline void
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 changed, GetLastError needs to be preserved and reset before returning.
if (alertable && info) {
leave_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 changed, GetLastError needs to be preserved and reset before returning.
if (alertable && info) {
leave_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 changed, GetLastError needs to be preserved and reset before returning.
if (alertable && info) {
leave_alertable_wait (info);
}
enter_alertable_wait (info);
}
- result = SignalObjectAndWait (toSignal, towlower, timeout, alertable);
+ result = SignalObjectAndWait (toSignal, toWait, timeout, alertable);
// NOTE, leave_alertable_wait should not affect GetLastError but
- // if changed, last error need to be preserved and reset before returning.
+ // if changed, GetLastError needs to be preserved and reset before returning.
if (alertable && info) {
leave_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 changed, GetLastError needs to be preserved and reset before returning.
if (alertable && info) {
leave_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 changed, GetLastError needs to be preserved and reset before returning.
if (alertable && info) {
leave_alertable_wait (info);
}