1 /**
2 * \file
3 * Win32 OS wait wrappers and interrupt/abort APC handling.
4 *
5 * Author:
6 *   Johan Lorensson (lateralusx.github@gmail.com)
7 *
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9 */
10 
11 #include <mono/utils/mono-os-wait.h>
12 #include <mono/utils/mono-threads.h>
13 #include <mono/utils/mono-threads-debug.h>
14 
15 enum ThreadWaitInfo {
16 	THREAD_WAIT_INFO_CLEARED = 0,
17 	THREAD_WAIT_INFO_ALERTABLE_WAIT_SLOT = 1 << 0,
18 	THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT = 1 << 1,
19 	THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT = 1 << 2
20 };
21 
22 static inline void
request_interrupt(gpointer thread_info,HANDLE native_thread_handle,gint32 pending_apc_slot,PAPCFUNC apc_callback,DWORD tid)23 request_interrupt (gpointer thread_info, HANDLE native_thread_handle, gint32 pending_apc_slot, PAPCFUNC apc_callback, DWORD tid)
24 {
25 	/*
26 	* On Windows platforms, an async interrupt/abort request queues an APC
27 	* that needs to be processed by target thread before it can return from an
28 	* alertable OS wait call and complete the mono interrupt/abort request.
29 	* Uncontrolled queuing of APC's could flood the APC queue preventing the target thread
30 	* to return from its alertable OS wait call, blocking the interrupt/abort requests to complete
31 	* This check makes sure that only one APC per type gets queued, preventing potential flooding
32 	* of the APC queue. NOTE, this code will execute regardless if targeted thread is currently in
33 	* an alertable wait or not. This is done to prevent races between interrupt/abort requests and
34 	* alertable wait calls. Threads already in an alertable wait should handle WAIT_IO_COMPLETION
35 	* return scenarios and restart the alertable wait operation if needed or take other actions
36 	* (like service the interrupt/abort request).
37 	*/
38 	MonoThreadInfo *info = (MonoThreadInfo *)thread_info;
39 	gint32 old_wait_info, new_wait_info;
40 
41 	do {
42 		old_wait_info = mono_atomic_load_i32 (&info->thread_wait_info);
43 		if (old_wait_info & pending_apc_slot)
44 			return;
45 
46 		new_wait_info = old_wait_info | pending_apc_slot;
47 	} while (mono_atomic_cas_i32 (&info->thread_wait_info, new_wait_info, old_wait_info) != old_wait_info);
48 
49 	THREADS_INTERRUPT_DEBUG ("%06d - Interrupting/Aborting syscall in thread %06d", GetCurrentThreadId (), tid);
50 	QueueUserAPC (apc_callback, native_thread_handle, (ULONG_PTR)NULL);
51 }
52 
53 static void CALLBACK
interrupt_apc(ULONG_PTR param)54 interrupt_apc (ULONG_PTR param)
55 {
56 	THREADS_INTERRUPT_DEBUG ("%06d - interrupt_apc () called", GetCurrentThreadId ());
57 }
58 
59 void
mono_win32_interrupt_wait(PVOID thread_info,HANDLE native_thread_handle,DWORD tid)60 mono_win32_interrupt_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid)
61 {
62 	request_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_INTERRUPT_APC_SLOT, interrupt_apc, tid);
63 }
64 
65 static void CALLBACK
abort_apc(ULONG_PTR param)66 abort_apc (ULONG_PTR param)
67 {
68 	THREADS_INTERRUPT_DEBUG ("%06d - abort_apc () called", GetCurrentThreadId ());
69 }
70 
71 void
mono_win32_abort_wait(PVOID thread_info,HANDLE native_thread_handle,DWORD tid)72 mono_win32_abort_wait (PVOID thread_info, HANDLE native_thread_handle, DWORD tid)
73 {
74 	request_interrupt (thread_info, native_thread_handle, THREAD_WAIT_INFO_PENDING_ABORT_APC_SLOT, abort_apc, tid);
75 }
76 
77 static inline void
enter_alertable_wait(MonoThreadInfo * info)78 enter_alertable_wait (MonoThreadInfo *info)
79 {
80 	// Clear any previous flags. Set alertable wait flag.
81 	mono_atomic_xchg_i32 (&info->thread_wait_info, THREAD_WAIT_INFO_ALERTABLE_WAIT_SLOT);
82 }
83 
84 static inline void
leave_alertable_wait(MonoThreadInfo * info)85 leave_alertable_wait (MonoThreadInfo *info)
86 {
87 	// Clear any previous flags. Thread is exiting alertable wait state, and info around pending interrupt/abort APC's
88 	// can now be discarded as well, thread is out of wait operation and can proceed it's execution.
89 	mono_atomic_xchg_i32 (&info->thread_wait_info, THREAD_WAIT_INFO_CLEARED);
90 }
91 
92 DWORD
mono_win32_sleep_ex(DWORD timeout,BOOL alertable)93 mono_win32_sleep_ex (DWORD timeout, BOOL alertable)
94 {
95 	DWORD result = WAIT_FAILED;
96 	MonoThreadInfo *info = mono_thread_info_current_unchecked ();
97 
98 	if (alertable && info) {
99 		enter_alertable_wait (info);
100 	}
101 
102 	result = SleepEx (timeout, alertable);
103 
104 	// NOTE, leave_alertable_wait should not affect GetLastError but
105 	// if changed, GetLastError needs to be preserved and reset before returning.
106 	if (alertable && info) {
107 		leave_alertable_wait (info);
108 	}
109 
110 	return result;
111 }
112 
113 DWORD
mono_win32_wait_for_single_object_ex(HANDLE handle,DWORD timeout,BOOL alertable)114 mono_win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertable)
115 {
116 	DWORD result = WAIT_FAILED;
117 	MonoThreadInfo *info = mono_thread_info_current_unchecked ();
118 
119 	if (alertable && info) {
120 		enter_alertable_wait (info);
121 	}
122 
123 	result = WaitForSingleObjectEx (handle, timeout, alertable);
124 
125 	// NOTE, leave_alertable_wait should not affect GetLastError but
126 	// if changed, GetLastError needs to be preserved and reset before returning.
127 	if (alertable && info) {
128 		leave_alertable_wait (info);
129 	}
130 
131 	return result;
132 }
133 
134 DWORD
mono_win32_wait_for_multiple_objects_ex(DWORD count,CONST HANDLE * handles,BOOL waitAll,DWORD timeout,BOOL alertable)135 mono_win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOOL waitAll, DWORD timeout, BOOL alertable)
136 {
137 	DWORD result = WAIT_FAILED;
138 	MonoThreadInfo *info = mono_thread_info_current_unchecked ();
139 
140 	if (alertable && info) {
141 		enter_alertable_wait (info);
142 	}
143 
144 	result = WaitForMultipleObjectsEx (count, handles, waitAll, timeout, alertable);
145 
146 	// NOTE, leave_alertable_wait should not affect GetLastError but
147 	// if changed, GetLastError needs to be preserved and reset before returning.
148 	if (alertable && info) {
149 		leave_alertable_wait (info);
150 	}
151 
152 	return result;
153 }
154 
155 DWORD
mono_win32_signal_object_and_wait(HANDLE toSignal,HANDLE toWait,DWORD timeout,BOOL alertable)156 mono_win32_signal_object_and_wait (HANDLE toSignal, HANDLE toWait, DWORD timeout, BOOL alertable)
157 {
158 	DWORD result = WAIT_FAILED;
159 	MonoThreadInfo *info = mono_thread_info_current_unchecked ();
160 
161 	if (alertable && info) {
162 		enter_alertable_wait (info);
163 	}
164 
165 	result = SignalObjectAndWait (toSignal, toWait, timeout, alertable);
166 
167 	// NOTE, leave_alertable_wait should not affect GetLastError but
168 	// if changed, GetLastError needs to be preserved and reset before returning.
169 	if (alertable && info) {
170 		leave_alertable_wait (info);
171 	}
172 
173 	return result;
174 }
175 
176 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
177 DWORD
mono_win32_msg_wait_for_multiple_objects_ex(DWORD count,CONST HANDLE * handles,DWORD timeout,DWORD wakeMask,DWORD flags)178 mono_win32_msg_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, DWORD timeout, DWORD wakeMask, DWORD flags)
179 {
180 	DWORD result = WAIT_FAILED;
181 	MonoThreadInfo *info = mono_thread_info_current_unchecked ();
182 	BOOL alertable = flags & MWMO_ALERTABLE;
183 
184 	if (alertable && info) {
185 		enter_alertable_wait (info);
186 	}
187 
188 	result = MsgWaitForMultipleObjectsEx (count, handles, timeout, wakeMask, flags);
189 
190 	// NOTE, leave_alertable_wait should not affect GetLastError but
191 	// if changed, GetLastError needs to be preserved and reset before returning.
192 	if (alertable && info) {
193 		leave_alertable_wait (info);
194 	}
195 
196 	return result;
197 }
198 #endif
199 
200 DWORD
mono_win32_wsa_wait_for_multiple_events(DWORD count,const WSAEVENT FAR * handles,BOOL waitAll,DWORD timeout,BOOL alertable)201 mono_win32_wsa_wait_for_multiple_events (DWORD count, const WSAEVENT FAR *handles, BOOL waitAll, DWORD timeout, BOOL alertable)
202 {
203 	DWORD result = WAIT_FAILED;
204 	MonoThreadInfo *info = mono_thread_info_current_unchecked ();
205 
206 	if (alertable && info) {
207 		enter_alertable_wait (info);
208 	}
209 
210 	result = WSAWaitForMultipleEvents (count, handles, waitAll, timeout, alertable);
211 
212 	// NOTE, leave_alertable_wait should not affect GetLastError but
213 	// if changed, GetLastError needs to be preserved and reset before returning.
214 	if (alertable && info) {
215 		leave_alertable_wait (info);
216 	}
217 
218 	return result;
219 }
220