3 * Win32 OS wait wrappers and interrupt/abort APC handling.
6 * Johan Lorensson (lateralusx.github@gmail.com)
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
11 #include "mono-os-wait.h"
12 #include "mono-threads.h"
13 #include "mono-threads-debug.h"
14 #include "mono-logger-internals.h"
15 #include "mono-error-internals.h"
16 #include <mono/metadata/w32subset.h>
17 #include <mono/utils/checked-build.h>
19 /* Empty handler only used to detect interrupt state of current thread. */
20 /* Needed in order to correctly avoid entering wait methods under */
21 /* cooperative suspend of a thread. Under preemptive suspend a thread gets */
22 /* a queued APC as part of an alertable suspend request. The APC will break any wait's */
23 /* done by any of below methods. In hybrid suspend, if a thread gets into a GC safe area */
24 /* thread will be preemptive suspend as above and an APC will be queued, breaking any wait. */
25 /* If the thread is not within a GC safe area, a cooperative suspend will be used, but that */
26 /* won't queue an APC to the thread, so in cases where we enter a GC safe area and a wait */
27 /* using below functions, that wait won't be alerted. This could be solved using */
28 /* interrupt handlers. Problem with interrupt handlers on Windows together with APC is race */
29 /* between thread executing interrupt handler and current thread. We will need the thread */
30 /* alive when posting the APC, but since there is no synchronization between waiting thread */
31 /* and thread running interrupt handler, waiting thread could already be terminated when executing */
32 /* interrupt handler. There are ways to mitigate this, but using below schema is more lightweight and */
33 /* solves the same problem + gives some additional benefits on preemptive suspend. Wait methods */
34 /* will register a empty interrupt handler. This is needed in order to correctly get current alertable */
35 /* state of the thread when register/unregister handler. If thread is already interrupted, we can */
36 /* ignore call to wait method and return alertable error code. This solves the cooperative suspend */
37 /* scenario since we evaluate the current interrupt state inside GC safe block. If not yet interrupted, */
38 /* cooperative suspend will detect that thread is inside a GC safe block so it will interrupt kernel */
39 /* as part of suspend request (similar to preemptive suspend) queuing APC, breaking any waits. */
41 win32_wait_interrupt_handler (gpointer ignored
)
45 /* Evaluate if we have a pending interrupt on current thread before */
46 /* entering wait. If thread has been cooperative suspended, it won't */
47 /* always queue an APC (only when already in a GC safe block), but since */
48 /* we should be inside a GC safe block at this point, checking current */
49 /* thread's interrupt state will tell us if we have a pending interrupt. */
50 /* If not, we will get an APC queued to break any waits if interrupted */
51 /* after this check (both in cooperative and preemptive suspend modes). */
52 #define WIN32_CHECK_INTERRUPT(info, alertable) \
54 MONO_REQ_GC_SAFE_MODE; \
55 if (alertable && info && mono_thread_info_is_interrupt_state (info)) { \
56 SetLastError (WAIT_IO_COMPLETION); \
57 return WAIT_IO_COMPLETION; \
61 #define WIN32_ENTER_ALERTABLE_WAIT(info) \
64 gboolean alerted = FALSE; \
65 mono_thread_info_install_interrupt (win32_wait_interrupt_handler, NULL, &alerted); \
67 SetLastError (WAIT_IO_COMPLETION); \
68 return WAIT_IO_COMPLETION; \
70 mono_win32_enter_alertable_wait (info); \
74 #define WIN32_LEAVE_ALERTABLE_WAIT(info) \
77 gboolean alerted = FALSE; \
78 mono_win32_leave_alertable_wait (info); \
79 mono_thread_info_uninstall_interrupt (&alerted); \
84 win32_sleep_ex_interrupt_checked (MonoThreadInfo
*info
, DWORD timeout
, BOOL alertable
)
86 WIN32_CHECK_INTERRUPT (info
, alertable
);
87 return SleepEx (timeout
, alertable
);
91 win32_sleep_ex (DWORD timeout
, BOOL alertable
, BOOL cooperative
)
93 DWORD result
= WAIT_FAILED
;
94 MonoThreadInfo
* const info
= alertable
? mono_thread_info_current_unchecked () : NULL
;
96 WIN32_ENTER_ALERTABLE_WAIT (info
);
100 result
= win32_sleep_ex_interrupt_checked (info
, timeout
, alertable
);
103 result
= win32_sleep_ex_interrupt_checked (info
, timeout
, alertable
);
106 WIN32_LEAVE_ALERTABLE_WAIT (info
);
112 mono_win32_sleep_ex (DWORD timeout
, BOOL alertable
)
114 return win32_sleep_ex (timeout
, alertable
, FALSE
);
118 mono_coop_win32_sleep_ex (DWORD timeout
, BOOL alertable
)
120 return win32_sleep_ex (timeout
, alertable
, TRUE
);
124 win32_wait_for_single_object_ex_interrupt_checked (MonoThreadInfo
*info
, HANDLE handle
, DWORD timeout
, BOOL alertable
)
126 WIN32_CHECK_INTERRUPT (info
, alertable
);
127 return WaitForSingleObjectEx (handle
, timeout
, alertable
);
131 win32_wait_for_single_object_ex (HANDLE handle
, DWORD timeout
, BOOL alertable
, BOOL cooperative
)
133 DWORD result
= WAIT_FAILED
;
134 MonoThreadInfo
* const info
= alertable
? mono_thread_info_current_unchecked () : NULL
;
136 WIN32_ENTER_ALERTABLE_WAIT (info
);
140 result
= win32_wait_for_single_object_ex_interrupt_checked (info
, handle
, timeout
, alertable
);
143 result
= win32_wait_for_single_object_ex_interrupt_checked (info
, handle
, timeout
, alertable
);
146 WIN32_LEAVE_ALERTABLE_WAIT (info
);
152 mono_win32_wait_for_single_object_ex (HANDLE handle
, DWORD timeout
, BOOL alertable
)
154 return win32_wait_for_single_object_ex (handle
, timeout
, alertable
, FALSE
);
158 mono_coop_win32_wait_for_single_object_ex (HANDLE handle
, DWORD timeout
, BOOL alertable
)
160 return win32_wait_for_single_object_ex (handle
, timeout
, alertable
, TRUE
);
164 win32_wait_for_multiple_objects_ex_interrupt_checked (MonoThreadInfo
*info
, DWORD count
, CONST HANDLE
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
)
166 WIN32_CHECK_INTERRUPT (info
, alertable
);
167 return WaitForMultipleObjectsEx (count
, handles
, waitAll
, timeout
, alertable
);
171 win32_wait_for_multiple_objects_ex (DWORD count
, CONST HANDLE
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
, MonoError
*error
, BOOL cooperative
)
173 DWORD result
= WAIT_FAILED
;
174 MonoThreadInfo
* const info
= alertable
? mono_thread_info_current_unchecked () : NULL
;
176 WIN32_ENTER_ALERTABLE_WAIT (info
);
180 result
= win32_wait_for_multiple_objects_ex_interrupt_checked (info
, count
, handles
, waitAll
, timeout
, alertable
);
183 result
= win32_wait_for_multiple_objects_ex_interrupt_checked (info
, count
, handles
, waitAll
, timeout
, alertable
);
186 WIN32_LEAVE_ALERTABLE_WAIT (info
);
188 // This is not perfect, but it is the best you can do in usermode and matches CoreCLR.
189 // i.e. handle-based instead of object-based.
191 if (result
== WAIT_FAILED
&& waitAll
&& error
&&
192 count
> 1 && count
<= MAXIMUM_WAIT_OBJECTS
193 && GetLastError () == ERROR_INVALID_PARAMETER
) {
194 gpointer handles_sorted
[MAXIMUM_WAIT_OBJECTS
]; // 64
195 memcpy (handles_sorted
, handles
, count
* sizeof (handles
[0]));
196 qsort (handles_sorted
, count
, sizeof (handles_sorted
[0]), g_direct_equal
);
197 for (DWORD i
= 1; i
< count
; ++i
) {
198 if (handles_sorted
[i
- 1] == handles_sorted
[i
]) {
199 mono_error_set_duplicate_wait_object (error
);
200 mono_trace (G_LOG_LEVEL_DEBUG
, MONO_TRACE_IO_LAYER_HANDLE
, "%s: handle %p is duplicated", __func__
, handles_sorted
[i
]);
201 // Preserve LastError, but reduce triggering write breakpoints.
202 if (GetLastError () != ERROR_INVALID_PARAMETER
)
203 SetLastError (ERROR_INVALID_PARAMETER
);
213 mono_win32_wait_for_multiple_objects_ex (DWORD count
, CONST HANDLE
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
, MonoError
*error
)
215 return win32_wait_for_multiple_objects_ex (count
, handles
, waitAll
, timeout
, alertable
, error
, FALSE
);
219 mono_coop_win32_wait_for_multiple_objects_ex (DWORD count
, CONST HANDLE
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
, MonoError
*error
)
221 return win32_wait_for_multiple_objects_ex (count
, handles
, waitAll
, timeout
, alertable
, error
, TRUE
);
224 #if HAVE_API_SUPPORT_WIN32_SIGNAL_OBJECT_AND_WAIT
227 win32_signal_object_and_wait_interrupt_checked (MonoThreadInfo
*info
, HANDLE toSignal
, HANDLE toWait
, DWORD timeout
, BOOL alertable
)
229 WIN32_CHECK_INTERRUPT (info
, alertable
);
230 return SignalObjectAndWait (toSignal
, toWait
, timeout
, alertable
);
234 win32_signal_object_and_wait (HANDLE toSignal
, HANDLE toWait
, DWORD timeout
, BOOL alertable
, BOOL cooperative
)
236 DWORD result
= WAIT_FAILED
;
237 MonoThreadInfo
* const info
= alertable
? mono_thread_info_current_unchecked () : NULL
;
239 WIN32_ENTER_ALERTABLE_WAIT (info
);
243 result
= win32_signal_object_and_wait_interrupt_checked (info
, toSignal
, toWait
, timeout
, alertable
);
246 result
= win32_signal_object_and_wait_interrupt_checked (info
, toSignal
, toWait
, timeout
, alertable
);
249 WIN32_LEAVE_ALERTABLE_WAIT (info
);
255 mono_win32_signal_object_and_wait (HANDLE toSignal
, HANDLE toWait
, DWORD timeout
, BOOL alertable
)
257 return win32_signal_object_and_wait (toSignal
, toWait
, timeout
, alertable
, FALSE
);
261 mono_coop_win32_signal_object_and_wait (HANDLE toSignal
, HANDLE toWait
, DWORD timeout
, BOOL alertable
)
263 return win32_signal_object_and_wait (toSignal
, toWait
, timeout
, alertable
, TRUE
);
266 #endif /* HAVE_API_SUPPORT_WIN32_SIGNAL_OBJECT_AND_WAIT */
268 #if HAVE_API_SUPPORT_WIN32_MSG_WAIT_FOR_MULTIPLE_OBJECTS
270 win32_msg_wait_for_multiple_objects_ex_interrupt_checked (MonoThreadInfo
*info
, DWORD count
, CONST HANDLE
*handles
, DWORD timeout
, DWORD wakeMask
, DWORD flags
, BOOL alertable
)
272 WIN32_CHECK_INTERRUPT (info
, alertable
);
273 return MsgWaitForMultipleObjectsEx (count
, handles
, timeout
, wakeMask
, flags
);
277 win32_msg_wait_for_multiple_objects_ex (DWORD count
, CONST HANDLE
*handles
, DWORD timeout
, DWORD wakeMask
, DWORD flags
, BOOL cooperative
)
279 DWORD result
= WAIT_FAILED
;
280 BOOL alertable
= flags
& MWMO_ALERTABLE
;
281 MonoThreadInfo
* const info
= alertable
? mono_thread_info_current_unchecked () : NULL
;
283 WIN32_ENTER_ALERTABLE_WAIT (info
);
287 result
= win32_msg_wait_for_multiple_objects_ex_interrupt_checked (info
, count
, handles
, timeout
, wakeMask
, flags
, alertable
);
290 result
= win32_msg_wait_for_multiple_objects_ex_interrupt_checked (info
, count
, handles
, timeout
, wakeMask
, flags
, alertable
);
293 WIN32_LEAVE_ALERTABLE_WAIT (info
);
299 mono_win32_msg_wait_for_multiple_objects_ex (DWORD count
, CONST HANDLE
*handles
, DWORD timeout
, DWORD wakeMask
, DWORD flags
)
301 return win32_msg_wait_for_multiple_objects_ex (count
, handles
, timeout
, wakeMask
, flags
, FALSE
);
305 mono_coop_win32_msg_wait_for_multiple_objects_ex (DWORD count
, CONST HANDLE
*handles
, DWORD timeout
, DWORD wakeMask
, DWORD flags
)
307 return win32_msg_wait_for_multiple_objects_ex (count
, handles
, timeout
, wakeMask
, flags
, TRUE
);
309 #endif /* HAVE_API_SUPPORT_WIN32_MSG_WAIT_FOR_MULTIPLE_OBJECTS */
312 win32_wsa_wait_for_multiple_events_interrupt_checked (MonoThreadInfo
*info
, DWORD count
, const WSAEVENT FAR
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
)
314 WIN32_CHECK_INTERRUPT (info
, alertable
);
315 return WSAWaitForMultipleEvents (count
, handles
, waitAll
, timeout
, alertable
);
319 win32_wsa_wait_for_multiple_events (DWORD count
, const WSAEVENT FAR
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
, BOOL cooperative
)
321 DWORD result
= WAIT_FAILED
;
322 MonoThreadInfo
* const info
= alertable
? mono_thread_info_current_unchecked () : NULL
;
324 WIN32_ENTER_ALERTABLE_WAIT (info
);
328 result
= win32_wsa_wait_for_multiple_events_interrupt_checked (info
, count
, handles
, waitAll
, timeout
, alertable
);
331 result
= win32_wsa_wait_for_multiple_events_interrupt_checked (info
, count
, handles
, waitAll
, timeout
, alertable
);
334 WIN32_LEAVE_ALERTABLE_WAIT (info
);
340 mono_win32_wsa_wait_for_multiple_events (DWORD count
, const WSAEVENT FAR
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
)
342 return win32_wsa_wait_for_multiple_events (count
, handles
, waitAll
, timeout
, alertable
, FALSE
);
346 mono_coop_win32_wsa_wait_for_multiple_events (DWORD count
, const WSAEVENT FAR
*handles
, BOOL waitAll
, DWORD timeout
, BOOL alertable
)
348 return win32_wsa_wait_for_multiple_events (count
, handles
, waitAll
, timeout
, alertable
, TRUE
);