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