3 * Low-level threading, windows version
6 * Rodrigo Kumpera (kumpera@gmail.com)
11 #include <mono/utils/mono-threads.h>
13 #if defined(USE_WINDOWS_BACKEND)
16 #include <mono/utils/mono-compiler.h>
17 #include <mono/utils/mono-threads-coop.h>
18 #include <mono/utils/mono-threads-debug.h>
19 #include <mono/utils/mono-os-wait.h>
20 #include <mono/metadata/w32subset.h>
24 WIN32_APC_INFO_CLEARED
= 0,
25 WIN32_APC_INFO_ALERTABLE_WAIT_SLOT
= 1 << 0,
26 WIN32_APC_INFO_BLOCKING_IO_SLOT
= 1 << 1,
27 WIN32_APC_INFO_PENDING_INTERRUPT_SLOT
= 1 << 2,
28 WIN32_APC_INFO_PENDING_ABORT_SLOT
= 1 << 3
32 request_interrupt (gpointer thread_info
, HANDLE native_thread_handle
, gint32 pending_apc_slot
, PAPCFUNC apc_callback
, DWORD tid
)
35 * On Windows platforms, an async interrupt/abort request queues an APC
36 * that needs to be processed by target thread before it can return from an
37 * alertable OS wait call and complete the mono interrupt/abort request.
38 * Uncontrolled queuing of APC's could flood the APC queue preventing the target thread
39 * to return from its alertable OS wait call, blocking the interrupt/abort requests to complete.
40 * This check makes sure that only one APC per type gets queued, preventing potential flooding
41 * of the APC queue. NOTE, this code will execute regardless if targeted thread is currently in
42 * an alertable wait or not. This is done to prevent races between interrupt/abort requests and
43 * alertable wait calls. Threads already in an alertable wait should handle WAIT_IO_COMPLETION
44 * return scenarios and restart the alertable wait operation if needed or take other actions
45 * (like service the interrupt/abort request).
47 MonoThreadInfo
*info
= (MonoThreadInfo
*)thread_info
;
48 gint32 old_apc_info
, new_apc_info
;
51 old_apc_info
= mono_atomic_load_i32 (&info
->win32_apc_info
);
52 if (old_apc_info
& pending_apc_slot
)
55 new_apc_info
= old_apc_info
| pending_apc_slot
;
56 } while (mono_atomic_cas_i32 (&info
->win32_apc_info
, new_apc_info
, old_apc_info
) != old_apc_info
);
58 THREADS_INTERRUPT_DEBUG ("%06d - Interrupting/Aborting syscall in thread %06d", GetCurrentThreadId (), tid
);
59 QueueUserAPC (apc_callback
, native_thread_handle
, (ULONG_PTR
)NULL
);
63 interrupt_apc (ULONG_PTR param
)
65 THREADS_INTERRUPT_DEBUG ("%06d - interrupt_apc () called", GetCurrentThreadId ());
69 mono_win32_interrupt_wait (PVOID thread_info
, HANDLE native_thread_handle
, DWORD tid
)
71 request_interrupt (thread_info
, native_thread_handle
, WIN32_APC_INFO_PENDING_INTERRUPT_SLOT
, interrupt_apc
, tid
);
75 abort_apc (ULONG_PTR param
)
77 THREADS_INTERRUPT_DEBUG ("%06d - abort_apc () called", GetCurrentThreadId ());
79 MonoThreadInfo
*info
= mono_thread_info_current_unchecked ();
81 // Check if pending interrupt is still relevant and current thread has not left alertable wait region.
82 // NOTE, can only be reset by current thread, currently running this APC.
83 gint32 win32_apc_info
= mono_atomic_load_i32 (&info
->win32_apc_info
);
84 if (win32_apc_info
& WIN32_APC_INFO_BLOCKING_IO_SLOT
) {
85 // Check if current thread registered an IO handle when entering alertable wait (blocking IO call).
86 // No need for CAS on win32_apc_info_io_handle since its only loaded/stored by current thread
87 // currently running APC.
88 HANDLE io_handle
= (HANDLE
)info
->win32_apc_info_io_handle
;
89 if (io_handle
!= INVALID_HANDLE_VALUE
) {
90 // In order to break IO waits, cancel all outstanding IO requests.
91 // Start to cancel IO requests for the registered IO handle issued by current thread.
92 // NOTE, this is NOT a blocking call.
99 // Attempt to cancel sync blocking IO on abort syscall requests.
100 // NOTE, the effect of the canceled IO operation is unknown so the caller need
101 // to close used resources (file, socket) to get back to a known state. The need
102 // to abort blocking IO calls is normally part of doing a thread abort, then the
103 // thread is going away meaning that no more IO calls will be issued against the
104 // same resource that was part of the cancelation. Current implementation of
105 // .NET Framework and .NET Core currently don't support the ability to abort a thread
106 // blocked on sync IO calls, see https://github.com/dotnet/corefx/issues/5749.
107 // Since there is no solution covering all scenarios aborting blocking syscall this
108 // will be on best effort and there might still be a slight risk that the blocking call
109 // won't abort (depending on overlapped IO support for current file, socket).
111 suspend_abort_syscall (PVOID thread_info
, HANDLE native_thread_handle
, DWORD tid
)
113 request_interrupt (thread_info
, native_thread_handle
, WIN32_APC_INFO_PENDING_ABORT_SLOT
, abort_apc
, tid
);
117 enter_alertable_wait_ex (MonoThreadInfo
*info
, HANDLE io_handle
)
119 // Only loaded/stored by current thread, here or in APC (also running on current thread).
120 g_assert (info
->win32_apc_info_io_handle
== (gpointer
)INVALID_HANDLE_VALUE
);
121 info
->win32_apc_info_io_handle
= io_handle
;
123 //Set alertable wait flag.
124 mono_atomic_xchg_i32 (&info
->win32_apc_info
, (io_handle
== INVALID_HANDLE_VALUE
) ? WIN32_APC_INFO_ALERTABLE_WAIT_SLOT
: WIN32_APC_INFO_BLOCKING_IO_SLOT
);
128 leave_alertable_wait_ex (MonoThreadInfo
*info
, HANDLE io_handle
)
130 // Clear any previous flags. Thread is exiting alertable wait region, and info around pending interrupt/abort APC's
131 // can now be discarded, thread is out of wait operation and can proceed execution.
132 mono_atomic_xchg_i32 (&info
->win32_apc_info
, WIN32_APC_INFO_CLEARED
);
134 // Only loaded/stored by current thread, here or in APC (also running on current thread).
135 g_assert (info
->win32_apc_info_io_handle
== io_handle
);
136 info
->win32_apc_info_io_handle
= (gpointer
)INVALID_HANDLE_VALUE
;
140 mono_win32_enter_alertable_wait (THREAD_INFO_TYPE
*info
)
143 enter_alertable_wait_ex (info
, INVALID_HANDLE_VALUE
);
147 mono_win32_leave_alertable_wait (THREAD_INFO_TYPE
*info
)
150 leave_alertable_wait_ex (info
, INVALID_HANDLE_VALUE
);
154 mono_win32_enter_blocking_io_call (THREAD_INFO_TYPE
*info
, HANDLE io_handle
)
157 enter_alertable_wait_ex (info
, io_handle
);
161 mono_win32_leave_blocking_io_call (THREAD_INFO_TYPE
*info
, HANDLE io_handle
)
164 leave_alertable_wait_ex (info
, io_handle
);
168 mono_threads_suspend_init (void)
173 mono_threads_suspend_begin_async_suspend (MonoThreadInfo
*info
, gboolean interrupt_kernel
)
175 DWORD id
= mono_thread_info_get_tid (info
);
179 handle
= info
->native_handle
;
182 result
= SuspendThread (handle
);
183 THREADS_SUSPEND_DEBUG ("SUSPEND %p -> %u\n", GUINT_TO_POINTER (id
), result
);
184 if (result
== (DWORD
)-1) {
185 if (!mono_threads_transition_abort_async_suspend (info
)) {
186 /* We raced with self suspend and lost so suspend can continue. */
187 g_assert (mono_threads_is_hybrid_suspension_enabled ());
188 info
->suspend_can_continue
= TRUE
;
189 THREADS_SUSPEND_DEBUG ("\tlost race with self suspend %p\n", mono_thread_info_get_tid (info
));
192 THREADS_SUSPEND_DEBUG ("SUSPEND FAILED, id=%p, err=%u\n", GUINT_TO_POINTER (id
), GetLastError ());
196 /* Suspend logic assumes thread is really suspended before continuing below. Surprisingly SuspendThread */
197 /* is just an async request to the scheduler, meaning that the suspended thread can continue to run */
198 /* user mode code until scheduler gets around and process the request. This will cause a thread state race */
199 /* in mono's thread state machine implementation on Windows. By requesting a threads context after issuing a */
200 /* suspended request, this will wait until thread is suspended and thread context has been collected */
203 context
.ContextFlags
= CONTEXT_INTEGER
| CONTEXT_CONTROL
;
204 if (!GetThreadContext (handle
, &context
)) {
205 result
= ResumeThread (handle
);
206 g_assert (result
== 1);
207 if (!mono_threads_transition_abort_async_suspend (info
)) {
208 /* We raced with self suspend and lost so suspend can continue. */
209 g_assert (mono_threads_is_hybrid_suspension_enabled ());
210 info
->suspend_can_continue
= TRUE
;
211 THREADS_SUSPEND_DEBUG ("\tlost race with self suspend %p\n", mono_thread_info_get_tid (info
));
214 THREADS_SUSPEND_DEBUG ("SUSPEND FAILED (GetThreadContext), id=%p, err=%u\n", GUINT_TO_POINTER (id
), GetLastError ());
218 if (!mono_threads_transition_finish_async_suspend (info
)) {
219 /* We raced with self-suspend and lost. Resume the native
220 * thread. It is still self-suspended, waiting to be resumed.
221 * So suspend can continue.
223 result
= ResumeThread (handle
);
224 g_assert (result
== 1);
225 info
->suspend_can_continue
= TRUE
;
226 THREADS_SUSPEND_DEBUG ("\tlost race with self suspend %p\n", GUINT_TO_POINTER (id
));
227 g_assert (mono_threads_is_hybrid_suspension_enabled ());
228 //XXX interrupt_kernel doesn't make sense in this case as the target is not in a syscall
231 info
->suspend_can_continue
= mono_threads_get_runtime_callbacks ()->thread_state_init_from_handle (&info
->thread_saved_state
[ASYNC_SUSPEND_STATE_INDEX
], info
, &context
);
232 THREADS_SUSPEND_DEBUG ("thread state %p -> %u\n", GUINT_TO_POINTER (id
), result
);
233 if (info
->suspend_can_continue
) {
234 if (interrupt_kernel
)
235 suspend_abort_syscall (info
, handle
, id
);
237 THREADS_SUSPEND_DEBUG ("FAILSAFE RESUME/2 %p -> %u\n", GUINT_TO_POINTER (id
), 0);
244 mono_threads_suspend_check_suspend_result (MonoThreadInfo
*info
)
246 return info
->suspend_can_continue
;
250 mono_threads_suspend_abort_syscall (MonoThreadInfo
*info
)
252 DWORD id
= mono_thread_info_get_tid(info
);
253 g_assert (info
->native_handle
);
254 suspend_abort_syscall (info
, info
->native_handle
, id
);
258 mono_win32_abort_blocking_io_call (MonoThreadInfo
*info
)
260 #if HAVE_API_SUPPORT_WIN32_CANCEL_SYNCHRONOUS_IO
261 // In case thread is blocked on sync IO preventing it from running above queued APC, cancel
262 // all outputstanding sync IO for target thread. If its not blocked on a sync IO request, below
263 // call will just fail and nothing will be canceled. If thread is waiting on overlapped IO,
264 // the queued APC will take care of cancel specific outstanding IO requests.
265 gint32 win32_apc_info
= mono_atomic_load_i32 (&info
->win32_apc_info
);
266 if (win32_apc_info
& WIN32_APC_INFO_BLOCKING_IO_SLOT
) {
267 CancelSynchronousIo (info
->native_handle
);
273 mono_threads_suspend_begin_async_resume (MonoThreadInfo
*info
)
275 DWORD id
= mono_thread_info_get_tid (info
);
279 handle
= info
->native_handle
;
282 if (info
->async_target
) {
283 #if HAVE_API_SUPPORT_WIN32_SET_THREAD_CONTEXT
288 ctx
= info
->thread_saved_state
[ASYNC_SUSPEND_STATE_INDEX
].ctx
;
289 mono_threads_get_runtime_callbacks ()->setup_async_callback (&ctx
, info
->async_target
, info
->user_data
);
290 info
->async_target
= NULL
;
291 info
->user_data
= NULL
;
293 context
.ContextFlags
= CONTEXT_INTEGER
| CONTEXT_CONTROL
;
295 if (!GetThreadContext (handle
, &context
)) {
296 THREADS_SUSPEND_DEBUG ("RESUME FAILED (GetThreadContext), id=%p, err=%u\n", GUINT_TO_POINTER (id
), GetLastError ());
300 g_assert (context
.ContextFlags
& CONTEXT_INTEGER
);
301 g_assert (context
.ContextFlags
& CONTEXT_CONTROL
);
303 mono_monoctx_to_sigctx (&ctx
, &context
);
305 context
.ContextFlags
= CONTEXT_INTEGER
| CONTEXT_CONTROL
;
306 res
= SetThreadContext (handle
, &context
);
308 THREADS_SUSPEND_DEBUG ("RESUME FAILED (SetThreadContext), id=%p, err=%u\n", GUINT_TO_POINTER (id
), GetLastError ());
312 g_error ("Not implemented due to lack of SetThreadContext");
316 result
= ResumeThread (handle
);
317 THREADS_SUSPEND_DEBUG ("RESUME %p -> %u\n", GUINT_TO_POINTER (id
), result
);
319 return result
!= (DWORD
)-1;
324 mono_threads_suspend_register (MonoThreadInfo
*info
)
326 g_assert (!info
->native_handle
);
327 info
->native_handle
= mono_threads_open_native_thread_handle (GetCurrentThread ());
331 mono_threads_suspend_free (MonoThreadInfo
*info
)
333 mono_threads_close_native_thread_handle (info
->native_handle
);
334 info
->native_handle
= NULL
;
338 mono_threads_suspend_init_signals (void)
343 mono_threads_suspend_search_alternative_signal (void)
345 g_assert_not_reached ();
349 mono_threads_suspend_get_suspend_signal (void)
355 mono_threads_suspend_get_restart_signal (void)
361 mono_threads_suspend_get_abort_signal (void)
368 #if defined (HOST_WIN32)
370 #define MONO_WIN32_DEFAULT_NATIVE_STACK_SIZE (1024 * 1024)
373 mono_thread_platform_create_thread (MonoThreadStart thread_fn
, gpointer thread_data
, gsize
* const stack_size
, MonoNativeThreadId
*tid
)
377 gsize set_stack_size
= MONO_WIN32_DEFAULT_NATIVE_STACK_SIZE
;
379 if (stack_size
&& *stack_size
)
380 set_stack_size
= *stack_size
;
382 result
= CreateThread (NULL
, set_stack_size
, (LPTHREAD_START_ROUTINE
) thread_fn
, thread_data
, 0, &thread_id
);
386 /* A new handle is open when attaching
387 * the thread, so we don't need this one */
388 CloseHandle (result
);
394 // TOOD: Use VirtualQuery to get correct value
395 // http://stackoverflow.com/questions/2480095/thread-stack-size-on-windows-visual-c
396 *stack_size
= set_stack_size
;
404 mono_native_thread_id_get (void)
406 return GetCurrentThreadId ();
410 mono_native_thread_os_id_get (void)
412 return (guint64
)GetCurrentThreadId ();
416 mono_native_thread_id_equals (MonoNativeThreadId id1
, MonoNativeThreadId id2
)
422 mono_native_thread_create (MonoNativeThreadId
*tid
, gpointer func
, gpointer arg
)
424 return CreateThread (NULL
, MONO_WIN32_DEFAULT_NATIVE_STACK_SIZE
, (LPTHREAD_START_ROUTINE
)func
, arg
, 0, tid
) != NULL
;
428 mono_native_thread_join_handle (HANDLE thread_handle
, gboolean close_handle
)
430 DWORD res
= WaitForSingleObject (thread_handle
, INFINITE
);
433 CloseHandle (thread_handle
);
435 return res
!= WAIT_FAILED
;
439 * Can't OpenThread on UWP until SDK 15063 (our minspec today is 10240),
440 * but this function doesn't seem to be used on Windows anyway
442 #if HAVE_API_SUPPORT_WIN32_OPEN_THREAD
444 mono_native_thread_join (MonoNativeThreadId tid
)
448 if (!(handle
= OpenThread (SYNCHRONIZE
, TRUE
, tid
)))
451 return mono_native_thread_join_handle (handle
, TRUE
);
456 mono_threads_platform_get_stack_bounds (guint8
**staddr
, size_t *stsize
)
458 #if _WIN32_WINNT >= 0x0602 // Windows 8 or newer and very fast, just a few instructions, no syscall.
461 GetCurrentThreadStackLimits (&low
, &high
);
462 *staddr
= (guint8
*)low
;
463 *stsize
= high
- low
;
464 #else // Win7 and older (or newer, still works, but much slower).
465 MEMORY_BASIC_INFORMATION info
;
466 // Windows stacks are commited on demand, one page at time.
467 // teb->StackBase is the top from which it grows down.
468 // teb->StackLimit is commited, the lowest it has gone so far.
469 // info.AllocationBase is reserved, the lowest it can go.
471 VirtualQuery (&info
, &info
, sizeof (info
));
472 *staddr
= (guint8
*)info
.AllocationBase
;
473 // TEB starts with TIB. TIB is public, TEB is not.
474 *stsize
= (size_t)((NT_TIB
*)NtCurrentTeb ())->StackBase
- (size_t)info
.AllocationBase
;
478 #if SIZEOF_VOID_P == 4 && HAVE_API_SUPPORT_WIN32_IS_WOW64_PROCESS
479 typedef BOOL (WINAPI
*LPFN_ISWOW64PROCESS
) (HANDLE
, PBOOL
);
480 static gboolean is_wow64
= FALSE
;
483 /* We do this at init time to avoid potential races with module opening */
485 mono_threads_platform_init (void)
487 #if SIZEOF_VOID_P == 4 && HAVE_API_SUPPORT_WIN32_IS_WOW64_PROCESS
488 LPFN_ISWOW64PROCESS is_wow64_func
= (LPFN_ISWOW64PROCESS
) GetProcAddress (GetModuleHandle (TEXT ("kernel32")), "IsWow64Process");
490 is_wow64_func (GetCurrentProcess (), &is_wow64
);
495 thread_is_cooperative_suspend_aware (MonoThreadInfo
*info
)
497 return (mono_threads_is_cooperative_suspension_enabled () || mono_atomic_load_i32 (&(info
->coop_aware_thread
)));
501 * When running x86 process under x64 system syscalls are done through WoW64. This
502 * needs to do a transition from x86 mode to x64 so it can syscall into the x64 system.
503 * Apparently this transition invalidates the ESP that we would get from calling
504 * GetThreadContext, so we would fail to scan parts of the thread stack. We attempt
505 * to query whether the thread is in such a transition so we try to restart it later.
506 * We check CONTEXT_EXCEPTION_ACTIVE for this, which is highly undocumented.
509 mono_threads_platform_in_critical_region (THREAD_INFO_TYPE
*info
)
511 gboolean ret
= FALSE
;
512 #if SIZEOF_VOID_P == 4 && HAVE_API_SUPPORT_WIN32_OPEN_THREAD
513 /* FIXME On cygwin these are not defined */
514 #if defined(CONTEXT_EXCEPTION_REQUEST) && defined(CONTEXT_EXCEPTION_REPORTING) && defined(CONTEXT_EXCEPTION_ACTIVE)
515 if (is_wow64
&& thread_is_cooperative_suspend_aware (info
)) {
516 /* Cooperative suspended threads will block at well-defined locations. */
518 } else if (is_wow64
&& mono_threads_is_hybrid_suspension_enabled ()) {
519 /* If thread is cooperative suspended, we shouldn't validate context */
520 /* since thread could still be running towards it's wait state. Calling */
521 /* GetThreadContext on a running thread (before calling SuspendThread) */
522 /* could return incorrect results and since cooperative suspended threads */
523 /* will block at well defined-locations, no need to do so. */
524 int thread_state
= mono_thread_info_current_state (info
);
525 if (thread_state
== STATE_SELF_SUSPENDED
|| thread_state
== STATE_BLOCKING_SELF_SUSPENDED
)
530 HANDLE handle
= OpenThread (THREAD_ALL_ACCESS
, FALSE
, mono_thread_info_get_tid (info
));
533 ZeroMemory (&context
, sizeof (CONTEXT
));
534 context
.ContextFlags
= CONTEXT_EXCEPTION_REQUEST
;
535 if (GetThreadContext (handle
, &context
)) {
536 if ((context
.ContextFlags
& CONTEXT_EXCEPTION_REPORTING
) &&
537 (context
.ContextFlags
& CONTEXT_EXCEPTION_ACTIVE
))
540 CloseHandle (handle
);
549 mono_threads_platform_yield (void)
551 return SwitchToThread ();
555 mono_threads_platform_exit (gsize exit_code
)
557 ExitThread (exit_code
);
561 mono_thread_info_get_system_max_stack_size (void)
568 mono_memory_barrier_process_wide (void)
570 FlushProcessWriteBuffers ();
575 #include <mono/utils/mono-compiler.h>
577 MONO_EMPTY_SOURCE_FILE (mono_threads_windows
);