[threads] Make OSEvent alertable to fix bug #51653 (#4347)
[mono.git] / mono / utils / os-event-unix.c
1 /*
2  * os-event-unix.c: MonoOSEvent on Unix
3  *
4  * Author:
5  *      Ludovic Henry (luhenry@microsoft.com)
6  *
7  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
8  */
9
10 #include "os-event.h"
11
12 #include "atomic.h"
13 #include "mono-lazy-init.h"
14 #include "mono-threads.h"
15 #include "mono-time.h"
16
17 static mono_lazy_init_t status = MONO_LAZY_INIT_STATUS_NOT_INITIALIZED;
18
19 static mono_mutex_t signal_mutex;
20
21 static void
22 initialize (void)
23 {
24         mono_os_mutex_init (&signal_mutex);
25 }
26
27 void
28 mono_os_event_init (MonoOSEvent *event, gboolean initial)
29 {
30         g_assert (event);
31
32         mono_lazy_initialize (&status, initialize);
33
34         event->conds = g_ptr_array_new ();
35         event->signalled = initial;
36 }
37
38 void
39 mono_os_event_destroy (MonoOSEvent *event)
40 {
41         g_assert (mono_lazy_is_initialized (&status));
42
43         g_assert (event);
44
45         if (event->conds->len > 0)
46                 g_error ("%s: cannot destroy osevent, there are still %d threads waiting on it", __func__, event->conds->len);
47
48         g_ptr_array_free (event->conds, TRUE);
49 }
50
51 static gboolean
52 mono_os_event_is_signalled (MonoOSEvent *event)
53 {
54         return event->signalled;
55 }
56
57 void
58 mono_os_event_set (MonoOSEvent *event)
59 {
60         gsize i;
61
62         g_assert (mono_lazy_is_initialized (&status));
63
64         g_assert (event);
65
66         mono_os_mutex_lock (&signal_mutex);
67
68         event->signalled = TRUE;
69
70         for (i = 0; i < event->conds->len; ++i)
71                 mono_os_cond_signal ((mono_cond_t*) event->conds->pdata [i]);
72
73         mono_os_mutex_unlock (&signal_mutex);
74 }
75
76 void
77 mono_os_event_reset (MonoOSEvent *event)
78 {
79         g_assert (mono_lazy_is_initialized (&status));
80
81         g_assert (event);
82
83         mono_os_mutex_lock (&signal_mutex);
84
85         event->signalled = FALSE;
86
87         mono_os_mutex_unlock (&signal_mutex);
88 }
89
90 MonoOSEventWaitRet
91 mono_os_event_wait_one (MonoOSEvent *event, guint32 timeout, gboolean alertable)
92 {
93         return mono_os_event_wait_multiple (&event, 1, TRUE, timeout, alertable);
94 }
95
96 typedef struct {
97         guint32 ref;
98         MonoOSEvent event;
99 } OSEventWaitData;
100
101 static void
102 signal_and_unref (gpointer user_data)
103 {
104         OSEventWaitData *data;
105
106         data = (OSEventWaitData*) user_data;
107
108         mono_os_event_set (&data->event);
109         if (InterlockedDecrement ((gint32*) &data->ref) == 0) {
110                 mono_os_event_destroy (&data->event);
111                 g_free (data);
112         }
113 }
114
115 MonoOSEventWaitRet
116 mono_os_event_wait_multiple (MonoOSEvent **events, gsize nevents, gboolean waitall, guint32 timeout, gboolean alertable)
117 {
118         MonoOSEventWaitRet ret;
119         mono_cond_t signal_cond;
120         OSEventWaitData *data;
121         gboolean alerted;
122         gint64 start;
123         gint i;
124
125         g_assert (mono_lazy_is_initialized (&status));
126
127         g_assert (events);
128         g_assert (nevents > 0);
129         g_assert (nevents <= MONO_OS_EVENT_WAIT_MAXIMUM_OBJECTS);
130
131         for (i = 0; i < nevents; ++i)
132                 g_assert (events [i]);
133
134         if (alertable) {
135                 data = g_new0 (OSEventWaitData, 1);
136                 data->ref = 2;
137                 mono_os_event_init (&data->event, FALSE);
138
139                 alerted = FALSE;
140                 mono_thread_info_install_interrupt (signal_and_unref, data, &alerted);
141                 if (alerted) {
142                         mono_os_event_destroy (&data->event);
143                         g_free (data);
144                         return MONO_OS_EVENT_WAIT_RET_ALERTED;
145                 }
146         }
147
148         if (timeout != MONO_INFINITE_WAIT)
149                 start = mono_msec_ticks ();
150
151         mono_os_cond_init (&signal_cond);
152
153         mono_os_mutex_lock (&signal_mutex);
154
155         for (i = 0; i < nevents; ++i)
156                 g_ptr_array_add (events [i]->conds, &signal_cond);
157
158         if (alertable)
159                 g_ptr_array_add (data->event.conds, &signal_cond);
160
161         for (;;) {
162                 gint count, lowest;
163                 gboolean signalled;
164
165                 count = 0;
166                 lowest = -1;
167
168                 for (i = 0; i < nevents; ++i) {
169                         if (mono_os_event_is_signalled (events [i])) {
170                                 count += 1;
171                                 if (lowest == -1)
172                                         lowest = i;
173                         }
174                 }
175
176                 if (alertable && mono_os_event_is_signalled (&data->event))
177                         signalled = TRUE;
178                 else if (waitall)
179                         signalled = (count == nevents);
180                 else /* waitany */
181                         signalled = (count > 0);
182
183                 if (signalled) {
184                         ret = MONO_OS_EVENT_WAIT_RET_SUCCESS_0 + lowest;
185                         goto done;
186                 }
187
188                 if (timeout == MONO_INFINITE_WAIT) {
189                         mono_os_cond_wait (&signal_cond, &signal_mutex);
190                 } else {
191                         gint64 elapsed;
192                         gint res;
193
194                         elapsed = mono_msec_ticks () - start;
195                         if (elapsed >= timeout) {
196                                 ret = MONO_OS_EVENT_WAIT_RET_TIMEOUT;
197                                 goto done;
198                         }
199
200                         res = mono_os_cond_timedwait (&signal_cond, &signal_mutex, timeout - elapsed);
201                         if (res != 0) {
202                                 ret = MONO_OS_EVENT_WAIT_RET_TIMEOUT;
203                                 goto done;
204                         }
205                 }
206         }
207
208 done:
209         for (i = 0; i < nevents; ++i)
210                 g_ptr_array_remove (events [i]->conds, &signal_cond);
211
212         if (alertable)
213                 g_ptr_array_remove (data->event.conds, &signal_cond);
214
215         mono_os_mutex_unlock (&signal_mutex);
216
217         mono_os_cond_destroy (&signal_cond);
218
219         if (alertable) {
220                 mono_thread_info_uninstall_interrupt (&alerted);
221                 if (alerted) {
222                         if (InterlockedDecrement ((gint32*) &data->ref) == 0) {
223                                 mono_os_event_destroy (&data->event);
224                                 g_free (data);
225                         }
226                         return MONO_OS_EVENT_WAIT_RET_ALERTED;
227                 }
228
229                 mono_os_event_destroy (&data->event);
230                 g_free (data);
231         }
232
233         return ret;
234 }