1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
|
/**
* \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-os-wait.h"
#include "mono-threads.h"
#include "mono-threads-debug.h"
#include "mono-logger-internals.h"
#include "mono-error-internals.h"
#include <mono/metadata/w32subset.h>
#include <mono/utils/checked-build.h>
/* Empty handler only used to detect interrupt state of current thread. */
/* Needed in order to correctly avoid entering wait methods under */
/* cooperative suspend of a thread. Under preemptive suspend a thread gets */
/* a queued APC as part of an alertable suspend request. The APC will break any wait's */
/* done by any of below methods. In hybrid suspend, if a thread gets into a GC safe area */
/* thread will be preemptive suspend as above and an APC will be queued, breaking any wait. */
/* If the thread is not within a GC safe area, a cooperative suspend will be used, but that */
/* won't queue an APC to the thread, so in cases where we enter a GC safe area and a wait */
/* using below functions, that wait won't be alerted. This could be solved using */
/* interrupt handlers. Problem with interrupt handlers on Windows together with APC is race */
/* between thread executing interrupt handler and current thread. We will need the thread */
/* alive when posting the APC, but since there is no synchronization between waiting thread */
/* and thread running interrupt handler, waiting thread could already be terminated when executing */
/* interrupt handler. There are ways to mitigate this, but using below schema is more lightweight and */
/* solves the same problem + gives some additional benefits on preemptive suspend. Wait methods */
/* will register a empty interrupt handler. This is needed in order to correctly get current alertable */
/* state of the thread when register/unregister handler. If thread is already interrupted, we can */
/* ignore call to wait method and return alertable error code. This solves the cooperative suspend */
/* scenario since we evaluate the current interrupt state inside GC safe block. If not yet interrupted, */
/* cooperative suspend will detect that thread is inside a GC safe block so it will interrupt kernel */
/* as part of suspend request (similar to preemptive suspend) queuing APC, breaking any waits. */
static void
win32_wait_interrupt_handler (gpointer ignored)
{
}
/* Evaluate if we have a pending interrupt on current thread before */
/* entering wait. If thread has been cooperative suspended, it won't */
/* always queue an APC (only when already in a GC safe block), but since */
/* we should be inside a GC safe block at this point, checking current */
/* thread's interrupt state will tell us if we have a pending interrupt. */
/* If not, we will get an APC queued to break any waits if interrupted */
/* after this check (both in cooperative and preemptive suspend modes). */
#define WIN32_CHECK_INTERRUPT(info, alertable) \
do { \
MONO_REQ_GC_SAFE_MODE; \
if (alertable && info && mono_thread_info_is_interrupt_state (info)) { \
SetLastError (WAIT_IO_COMPLETION); \
return WAIT_IO_COMPLETION; \
} \
} while (0)
#define WIN32_ENTER_ALERTABLE_WAIT(info) \
do { \
if (info) { \
gboolean alerted = FALSE; \
mono_thread_info_install_interrupt (win32_wait_interrupt_handler, NULL, &alerted); \
if (alerted) { \
SetLastError (WAIT_IO_COMPLETION); \
return WAIT_IO_COMPLETION; \
} \
mono_win32_enter_alertable_wait (info); \
} \
} while (0)
#define WIN32_LEAVE_ALERTABLE_WAIT(info) \
do { \
if (info) { \
gboolean alerted = FALSE; \
mono_win32_leave_alertable_wait (info); \
mono_thread_info_uninstall_interrupt (&alerted); \
} \
} while (0)
static DWORD
win32_sleep_ex_interrupt_checked (MonoThreadInfo *info, DWORD timeout, BOOL alertable)
{
WIN32_CHECK_INTERRUPT (info, alertable);
return SleepEx (timeout, alertable);
}
static DWORD
win32_sleep_ex (DWORD timeout, BOOL alertable, BOOL cooperative)
{
DWORD result = WAIT_FAILED;
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
WIN32_ENTER_ALERTABLE_WAIT (info);
if (cooperative) {
MONO_ENTER_GC_SAFE;
result = win32_sleep_ex_interrupt_checked (info, timeout, alertable);
MONO_EXIT_GC_SAFE;
} else {
result = win32_sleep_ex_interrupt_checked (info, timeout, alertable);
}
WIN32_LEAVE_ALERTABLE_WAIT (info);
return result;
}
DWORD
mono_win32_sleep_ex (DWORD timeout, BOOL alertable)
{
return win32_sleep_ex (timeout, alertable, FALSE);
}
DWORD
mono_coop_win32_sleep_ex (DWORD timeout, BOOL alertable)
{
return win32_sleep_ex (timeout, alertable, TRUE);
}
static DWORD
win32_wait_for_single_object_ex_interrupt_checked (MonoThreadInfo *info, HANDLE handle, DWORD timeout, BOOL alertable)
{
WIN32_CHECK_INTERRUPT (info, alertable);
return WaitForSingleObjectEx (handle, timeout, alertable);
}
static DWORD
win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertable, BOOL cooperative)
{
DWORD result = WAIT_FAILED;
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
WIN32_ENTER_ALERTABLE_WAIT (info);
if (cooperative) {
MONO_ENTER_GC_SAFE;
result = win32_wait_for_single_object_ex_interrupt_checked (info, handle, timeout, alertable);
MONO_EXIT_GC_SAFE;
} else {
result = win32_wait_for_single_object_ex_interrupt_checked (info, handle, timeout, alertable);
}
WIN32_LEAVE_ALERTABLE_WAIT (info);
return result;
}
DWORD
mono_win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertable)
{
return win32_wait_for_single_object_ex (handle, timeout, alertable, FALSE);
}
DWORD
mono_coop_win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertable)
{
return win32_wait_for_single_object_ex (handle, timeout, alertable, TRUE);
}
static DWORD
win32_wait_for_multiple_objects_ex_interrupt_checked (MonoThreadInfo *info, DWORD count, CONST HANDLE *handles, BOOL waitAll, DWORD timeout, BOOL alertable)
{
WIN32_CHECK_INTERRUPT (info, alertable);
return WaitForMultipleObjectsEx (count, handles, waitAll, timeout, alertable);
}
static DWORD
win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOOL waitAll, DWORD timeout, BOOL alertable, MonoError *error, BOOL cooperative)
{
DWORD result = WAIT_FAILED;
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
WIN32_ENTER_ALERTABLE_WAIT (info);
if (cooperative) {
MONO_ENTER_GC_SAFE;
result = win32_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, waitAll, timeout, alertable);
MONO_EXIT_GC_SAFE;
} else {
result = win32_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, waitAll, timeout, alertable);
}
WIN32_LEAVE_ALERTABLE_WAIT (info);
// This is not perfect, but it is the best you can do in usermode and matches CoreCLR.
// i.e. handle-based instead of object-based.
if (result == WAIT_FAILED && waitAll && error &&
count > 1 && count <= MAXIMUM_WAIT_OBJECTS
&& GetLastError () == ERROR_INVALID_PARAMETER) {
gpointer handles_sorted [MAXIMUM_WAIT_OBJECTS]; // 64
memcpy (handles_sorted, handles, count * sizeof (handles [0]));
mono_qsort (handles_sorted, count, sizeof (handles_sorted [0]), g_direct_equal);
for (DWORD i = 1; i < count; ++i) {
if (handles_sorted [i - 1] == handles_sorted [i]) {
mono_error_set_duplicate_wait_object (error);
mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_IO_LAYER_HANDLE, "%s: handle %p is duplicated", __func__, handles_sorted [i]);
// Preserve LastError, but reduce triggering write breakpoints.
if (GetLastError () != ERROR_INVALID_PARAMETER)
SetLastError (ERROR_INVALID_PARAMETER);
break;
}
}
}
return result;
}
DWORD
mono_win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOOL waitAll, DWORD timeout, BOOL alertable, MonoError *error)
{
return win32_wait_for_multiple_objects_ex (count, handles, waitAll, timeout, alertable, error, FALSE);
}
DWORD
mono_coop_win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOOL waitAll, DWORD timeout, BOOL alertable, MonoError *error)
{
return win32_wait_for_multiple_objects_ex (count, handles, waitAll, timeout, alertable, error, TRUE);
}
#if HAVE_API_SUPPORT_WIN32_SIGNAL_OBJECT_AND_WAIT
static DWORD
win32_signal_object_and_wait_interrupt_checked (MonoThreadInfo *info, HANDLE toSignal, HANDLE toWait, DWORD timeout, BOOL alertable)
{
WIN32_CHECK_INTERRUPT (info, alertable);
return SignalObjectAndWait (toSignal, toWait, timeout, alertable);
}
static DWORD
win32_signal_object_and_wait (HANDLE toSignal, HANDLE toWait, DWORD timeout, BOOL alertable, BOOL cooperative)
{
DWORD result = WAIT_FAILED;
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
WIN32_ENTER_ALERTABLE_WAIT (info);
if (cooperative) {
MONO_ENTER_GC_SAFE;
result = win32_signal_object_and_wait_interrupt_checked (info, toSignal, toWait, timeout, alertable);
MONO_EXIT_GC_SAFE;
} else {
result = win32_signal_object_and_wait_interrupt_checked (info, toSignal, toWait, timeout, alertable);
}
WIN32_LEAVE_ALERTABLE_WAIT (info);
return result;
}
DWORD
mono_win32_signal_object_and_wait (HANDLE toSignal, HANDLE toWait, DWORD timeout, BOOL alertable)
{
return win32_signal_object_and_wait (toSignal, toWait, timeout, alertable, FALSE);
}
DWORD
mono_coop_win32_signal_object_and_wait (HANDLE toSignal, HANDLE toWait, DWORD timeout, BOOL alertable)
{
return win32_signal_object_and_wait (toSignal, toWait, timeout, alertable, TRUE);
}
#endif /* HAVE_API_SUPPORT_WIN32_SIGNAL_OBJECT_AND_WAIT */
#if HAVE_API_SUPPORT_WIN32_MSG_WAIT_FOR_MULTIPLE_OBJECTS
static DWORD
win32_msg_wait_for_multiple_objects_ex_interrupt_checked (MonoThreadInfo *info, DWORD count, CONST HANDLE *handles, DWORD timeout, DWORD wakeMask, DWORD flags, BOOL alertable)
{
WIN32_CHECK_INTERRUPT (info, alertable);
return MsgWaitForMultipleObjectsEx (count, handles, timeout, wakeMask, flags);
}
static DWORD
win32_msg_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, DWORD timeout, DWORD wakeMask, DWORD flags, BOOL cooperative)
{
DWORD result = WAIT_FAILED;
BOOL alertable = flags & MWMO_ALERTABLE;
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
WIN32_ENTER_ALERTABLE_WAIT (info);
if (cooperative) {
MONO_ENTER_GC_SAFE;
result = win32_msg_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, timeout, wakeMask, flags, alertable);
MONO_EXIT_GC_SAFE;
} else {
result = win32_msg_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, timeout, wakeMask, flags, alertable);
}
WIN32_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)
{
return win32_msg_wait_for_multiple_objects_ex (count, handles, timeout, wakeMask, flags, FALSE);
}
DWORD
mono_coop_win32_msg_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, DWORD timeout, DWORD wakeMask, DWORD flags)
{
return win32_msg_wait_for_multiple_objects_ex (count, handles, timeout, wakeMask, flags, TRUE);
}
#endif /* HAVE_API_SUPPORT_WIN32_MSG_WAIT_FOR_MULTIPLE_OBJECTS */
static DWORD
win32_wsa_wait_for_multiple_events_interrupt_checked (MonoThreadInfo *info, DWORD count, const WSAEVENT FAR *handles, BOOL waitAll, DWORD timeout, BOOL alertable)
{
WIN32_CHECK_INTERRUPT (info, alertable);
return WSAWaitForMultipleEvents (count, handles, waitAll, timeout, alertable);
}
static DWORD
win32_wsa_wait_for_multiple_events (DWORD count, const WSAEVENT FAR *handles, BOOL waitAll, DWORD timeout, BOOL alertable, BOOL cooperative)
{
DWORD result = WAIT_FAILED;
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
WIN32_ENTER_ALERTABLE_WAIT (info);
if (cooperative) {
MONO_ENTER_GC_SAFE;
result = win32_wsa_wait_for_multiple_events_interrupt_checked (info, count, handles, waitAll, timeout, alertable);
MONO_EXIT_GC_SAFE;
} else {
result = win32_wsa_wait_for_multiple_events_interrupt_checked (info, count, handles, waitAll, timeout, alertable);
}
WIN32_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)
{
return win32_wsa_wait_for_multiple_events (count, handles, waitAll, timeout, alertable, FALSE);
}
DWORD
mono_coop_win32_wsa_wait_for_multiple_events (DWORD count, const WSAEVENT FAR *handles, BOOL waitAll, DWORD timeout, BOOL alertable)
{
return win32_wsa_wait_for_multiple_events (count, handles, waitAll, timeout, alertable, TRUE);
}
|