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