Skip to content

Commit ab3d27f

Browse files
authored
[Mono] Fix wait's returning false on non-runtime queued user APC's. (#116001)
Mono's internal interrupt handling uses user APC to break waits in case runtime needs to interrupt threads. If that happens, wait will return false. For API's like `ManualResetEventSlim.Wait()` with `void` return, this can cause issues if the wait is broken prematurely. If a user APC have been queued to the thread, not triggered by internal runtime interrupt handling, it would lead to unexpected side effects. Fix makes sure runtime ignores returns from WaitForSingleObjectEx/WaitForMultipleObjectsEx due to APC completions not initialized by runtime. Fix also adds a new runtime test to trigger this scenario for Mono runtime on Windows. Fixes #115178.
1 parent 7dce8f6 commit ab3d27f

File tree

6 files changed

+413
-34
lines changed

6 files changed

+413
-34
lines changed

src/mono/mono/metadata/monitor.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,7 @@ mono_monitor_wait (MonoObjectHandle obj_handle, guint32 ms, MonoBoolean allow_in
13451345
* is private to this thread. Therefore even if the event was
13461346
* signalled before we wait, we still succeed.
13471347
*/
1348-
ret = mono_w32handle_wait_one (event, ms, TRUE);
1348+
ret = mono_w32handle_wait_one (event, ms, allow_interruption);
13491349

13501350
/* Reset the thread state fairly early, so we don't have to worry
13511351
* about the monitor error checking
@@ -1390,6 +1390,10 @@ mono_monitor_wait (MonoObjectHandle obj_handle, guint32 ms, MonoBoolean allow_in
13901390
* wait queue
13911391
*/
13921392
mon->wait_list = g_slist_remove (mon->wait_list, event);
1393+
1394+
if (is_ok (error) && allow_interruption && ret == MONO_W32HANDLE_WAIT_RET_ALERTED)
1395+
mono_error_set_thread_interrupted (error);
1396+
13931397
}
13941398
mono_w32event_close (event);
13951399

src/mono/mono/utils/mono-os-wait-win32.c

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "mono-error-internals.h"
1616
#include <mono/utils/checked-build.h>
1717
#include <mono/utils/w32subset.h>
18+
#include <mono/utils/mono-time.h>
1819

1920
/* Empty handler only used to detect interrupt state of current thread. */
2021
/* Needed in order to correctly avoid entering wait methods under */
@@ -61,22 +62,23 @@ win32_wait_interrupt_handler (gpointer ignored)
6162
#define WIN32_ENTER_ALERTABLE_WAIT(info) \
6263
do { \
6364
if (info) { \
64-
gboolean alerted = FALSE; \
65-
mono_thread_info_install_interrupt (win32_wait_interrupt_handler, NULL, &alerted); \
66-
if (alerted) { \
65+
gboolean interrupted = FALSE; \
66+
mono_thread_info_install_interrupt (win32_wait_interrupt_handler, NULL, &interrupted); \
67+
if (interrupted) { \
6768
SetLastError (WAIT_IO_COMPLETION); \
6869
return WAIT_IO_COMPLETION; \
6970
} \
7071
mono_win32_enter_alertable_wait (info); \
7172
} \
7273
} while (0)
7374

74-
#define WIN32_LEAVE_ALERTABLE_WAIT(info) \
75+
#define WIN32_LEAVE_ALERTABLE_WAIT(info, alerted) \
7576
do { \
7677
if (info) { \
77-
gboolean alerted = FALSE; \
78-
mono_win32_leave_alertable_wait (info); \
79-
mono_thread_info_uninstall_interrupt (&alerted); \
78+
alerted = mono_win32_leave_alertable_wait (info); \
79+
gboolean interrupted; \
80+
mono_thread_info_uninstall_interrupt (&interrupted); \
81+
alerted = alerted || interrupted; \
8082
} \
8183
} while (0)
8284

@@ -93,17 +95,46 @@ win32_wait_for_single_object_ex (HANDLE handle, DWORD timeout, BOOL alertable, B
9395
DWORD result = WAIT_FAILED;
9496
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
9597

96-
WIN32_ENTER_ALERTABLE_WAIT (info);
98+
guint64 start_ticks = 0;
99+
gboolean done = FALSE;
97100

98-
if (cooperative) {
99-
MONO_ENTER_GC_SAFE;
100-
result = win32_wait_for_single_object_ex_interrupt_checked (info, handle, timeout, alertable);
101-
MONO_EXIT_GC_SAFE;
102-
} else {
103-
result = win32_wait_for_single_object_ex_interrupt_checked (info, handle, timeout, alertable);
104-
}
101+
if (timeout != INFINITE && alertable)
102+
start_ticks = GINT64_TO_UINT64 (mono_msec_ticks ());
103+
104+
while (!done)
105+
{
106+
DWORD current_timeout = timeout;
107+
108+
if (timeout != INFINITE && alertable && result == WAIT_IO_COMPLETION) {
109+
DWORD elapsed = 0;
110+
guint64 current_ticks = mono_msec_ticks ();
111+
112+
if (current_ticks >= start_ticks)
113+
elapsed = GUINT64_TO_UINT32 (current_ticks - start_ticks);
114+
else
115+
elapsed = GUINT64_TO_UINT32 (G_MAXUINT64 - start_ticks + current_ticks);
116+
117+
if (elapsed > timeout)
118+
return WAIT_TIMEOUT;
119+
120+
current_timeout = timeout - elapsed;
121+
}
122+
123+
WIN32_ENTER_ALERTABLE_WAIT (info);
124+
125+
if (cooperative) {
126+
MONO_ENTER_GC_SAFE;
127+
result = win32_wait_for_single_object_ex_interrupt_checked (info, handle, current_timeout, alertable);
128+
MONO_EXIT_GC_SAFE;
129+
} else {
130+
result = win32_wait_for_single_object_ex_interrupt_checked (info, handle, current_timeout, alertable);
131+
}
132+
133+
gboolean alerted = FALSE;
134+
WIN32_LEAVE_ALERTABLE_WAIT (info, alerted);
105135

106-
WIN32_LEAVE_ALERTABLE_WAIT (info);
136+
done = !(alertable && !alerted && result == WAIT_IO_COMPLETION);
137+
}
107138

108139
return result;
109140
}
@@ -133,17 +164,46 @@ win32_wait_for_multiple_objects_ex (DWORD count, CONST HANDLE *handles, BOOL wai
133164
DWORD result = WAIT_FAILED;
134165
MonoThreadInfo * const info = alertable ? mono_thread_info_current_unchecked () : NULL;
135166

136-
WIN32_ENTER_ALERTABLE_WAIT (info);
167+
guint64 start_ticks = 0;
168+
gboolean done = FALSE;
137169

138-
if (cooperative) {
139-
MONO_ENTER_GC_SAFE;
140-
result = win32_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, waitAll, timeout, alertable);
141-
MONO_EXIT_GC_SAFE;
142-
} else {
143-
result = win32_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, waitAll, timeout, alertable);
144-
}
170+
if (timeout != INFINITE && alertable)
171+
start_ticks = GINT64_TO_UINT64 (mono_msec_ticks ());
172+
173+
while (!done)
174+
{
175+
DWORD current_timeout = timeout;
176+
177+
if (timeout != INFINITE && alertable && result == WAIT_IO_COMPLETION) {
178+
DWORD elapsed = 0;
179+
guint64 current_ticks = mono_msec_ticks ();
180+
181+
if (current_ticks >= start_ticks)
182+
elapsed = GUINT64_TO_UINT32 (current_ticks - start_ticks);
183+
else
184+
elapsed = GUINT64_TO_UINT32 (G_MAXUINT64 - start_ticks + current_ticks);
185+
186+
if (elapsed > timeout)
187+
return WAIT_TIMEOUT;
188+
189+
current_timeout = timeout - elapsed;
190+
}
191+
192+
WIN32_ENTER_ALERTABLE_WAIT (info);
193+
194+
if (cooperative) {
195+
MONO_ENTER_GC_SAFE;
196+
result = win32_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, waitAll, current_timeout, alertable);
197+
MONO_EXIT_GC_SAFE;
198+
} else {
199+
result = win32_wait_for_multiple_objects_ex_interrupt_checked (info, count, handles, waitAll, current_timeout, alertable);
200+
}
201+
202+
gboolean alerted = FALSE;
203+
WIN32_LEAVE_ALERTABLE_WAIT (info, alerted);
145204

146-
WIN32_LEAVE_ALERTABLE_WAIT (info);
205+
done = !(alertable && !alerted && result == WAIT_IO_COMPLETION);
206+
}
147207

148208
// This is not perfect, but it is the best you can do in usermode and matches CoreCLR.
149209
// i.e. handle-based instead of object-based.

src/mono/mono/utils/mono-threads-windows.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,22 @@ enter_alertable_wait_ex (MonoThreadInfo *info, HANDLE io_handle)
131131
mono_atomic_xchg_i32 (&info->win32_apc_info, (io_handle == INVALID_HANDLE_VALUE) ? WIN32_APC_INFO_ALERTABLE_WAIT_SLOT : WIN32_APC_INFO_BLOCKING_IO_SLOT);
132132
}
133133

134-
static void
134+
static gboolean
135135
leave_alertable_wait_ex (MonoThreadInfo *info, HANDLE io_handle)
136136
{
137137
// Clear any previous flags. Thread is exiting alertable wait region, and info around pending interrupt/abort APC's
138138
// can now be discarded, thread is out of wait operation and can proceed execution.
139-
mono_atomic_xchg_i32 (&info->win32_apc_info, WIN32_APC_INFO_CLEARED);
139+
gint32 old = mono_atomic_xchg_i32 (&info->win32_apc_info, WIN32_APC_INFO_CLEARED);
140140

141141
// Only loaded/stored by current thread, here or in APC (also running on current thread).
142142
g_assert (info->win32_apc_info_io_handle == io_handle);
143143
info->win32_apc_info_io_handle = (gpointer)INVALID_HANDLE_VALUE;
144+
145+
gboolean alerted = FALSE;
146+
if (old & WIN32_APC_INFO_PENDING_INTERRUPT_SLOT || old & WIN32_APC_INFO_PENDING_ABORT_SLOT)
147+
alerted = TRUE;
148+
149+
return alerted;
144150
}
145151

146152
void
@@ -150,11 +156,14 @@ mono_win32_enter_alertable_wait (THREAD_INFO_TYPE *info)
150156
enter_alertable_wait_ex (info, INVALID_HANDLE_VALUE);
151157
}
152158

153-
void
159+
gboolean
154160
mono_win32_leave_alertable_wait (THREAD_INFO_TYPE *info)
155161
{
162+
gboolean alerted = FALSE;
156163
if (info)
157-
leave_alertable_wait_ex (info, INVALID_HANDLE_VALUE);
164+
alerted = leave_alertable_wait_ex (info, INVALID_HANDLE_VALUE);
165+
166+
return alerted;
158167
}
159168

160169
void
@@ -164,11 +173,14 @@ mono_win32_enter_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle)
164173
enter_alertable_wait_ex (info, io_handle);
165174
}
166175

167-
void
176+
gboolean
168177
mono_win32_leave_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle)
169178
{
179+
gboolean alerted = FALSE;
170180
if (info)
171-
leave_alertable_wait_ex (info, io_handle);
181+
alerted = leave_alertable_wait_ex (info, io_handle);
182+
183+
return alerted;
172184
}
173185

174186
void

src/mono/mono/utils/mono-threads.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -859,13 +859,13 @@ void mono_target_thread_schedule_synchronization_context(MonoNativeThreadId targ
859859
void
860860
mono_win32_enter_alertable_wait (THREAD_INFO_TYPE *info);
861861

862-
void
862+
gboolean
863863
mono_win32_leave_alertable_wait (THREAD_INFO_TYPE *info);
864864

865865
void
866866
mono_win32_enter_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle);
867867

868-
void
868+
gboolean
869869
mono_win32_leave_blocking_io_call (THREAD_INFO_TYPE *info, HANDLE io_handle);
870870

871871
void

0 commit comments

Comments
 (0)