* Makefile: Don't build make-map.exe.
[mono.git] / mono / io-layer / wait.c
1 /*
2  * wait.c:  wait for handles to become signalled
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2002-2006 Novell, Inc.
8  */
9
10 #include <config.h>
11 #include <glib.h>
12 #include <string.h>
13 #include <errno.h>
14
15 #include <mono/os/gc_wrapper.h>
16
17 #include <mono/io-layer/wapi.h>
18 #include <mono/io-layer/handles-private.h>
19 #include <mono/io-layer/wapi-private.h>
20 #include <mono/io-layer/mono-mutex.h>
21 #include <mono/io-layer/misc-private.h>
22
23 #undef DEBUG
24
25 static gboolean own_if_signalled(gpointer handle)
26 {
27         gboolean ret = FALSE;
28         
29         if (_WAPI_SHARED_HANDLE (_wapi_handle_type (handle))) {
30                 if (_wapi_handle_trylock_shared_handles () == EBUSY) {
31                         return (FALSE);
32                 }
33         }
34         
35         if (_wapi_handle_issignalled (handle)) {
36                 _wapi_handle_ops_own (handle);
37                 ret = TRUE;
38         }
39
40         if (_WAPI_SHARED_HANDLE (_wapi_handle_type (handle))) {
41                 _wapi_handle_unlock_shared_handles ();
42         }
43
44         return(ret);
45 }
46
47 static gboolean own_if_owned(gpointer handle)
48 {
49         gboolean ret = FALSE;
50         
51         if (_WAPI_SHARED_HANDLE (_wapi_handle_type (handle))) {
52                 if (_wapi_handle_trylock_shared_handles () == EBUSY) {
53                         return (FALSE);
54                 }
55         }
56         
57         if (_wapi_handle_ops_isowned (handle)) {
58                 _wapi_handle_ops_own (handle);
59                 ret = TRUE;
60         }
61
62         if (_WAPI_SHARED_HANDLE (_wapi_handle_type (handle))) {
63                 _wapi_handle_unlock_shared_handles ();
64         }
65
66         return(ret);
67 }
68
69 /**
70  * WaitForSingleObjectEx:
71  * @handle: an object to wait for
72  * @timeout: the maximum time in milliseconds to wait for
73  * @alertable: if TRUE, the wait can be interrupted by an APC call
74  *
75  * This function returns when either @handle is signalled, or @timeout
76  * ms elapses.  If @timeout is zero, the object's state is tested and
77  * the function returns immediately.  If @timeout is %INFINITE, the
78  * function waits forever.
79  *
80  * Return value: %WAIT_ABANDONED - @handle is a mutex that was not
81  * released by the owning thread when it exited.  Ownership of the
82  * mutex object is granted to the calling thread and the mutex is set
83  * to nonsignalled.  %WAIT_OBJECT_0 - The state of @handle is
84  * signalled.  %WAIT_TIMEOUT - The @timeout interval elapsed and
85  * @handle's state is still not signalled.  %WAIT_FAILED - an error
86  * occurred. %WAIT_IO_COMPLETION - the wait was ended by an APC.
87  */
88 guint32 WaitForSingleObjectEx(gpointer handle, guint32 timeout,
89                               gboolean alertable)
90 {
91         guint32 ret, waited;
92         struct timespec abstime;
93         int thr_ret;
94         gboolean apc_pending = FALSE;
95         gpointer current_thread = _wapi_thread_handle_from_id (pthread_self ());
96         
97         if (current_thread == NULL) {
98                 SetLastError (ERROR_INVALID_HANDLE);
99                 return(WAIT_FAILED);
100         }
101
102         if (handle == _WAPI_THREAD_CURRENT) {
103                 handle = _wapi_thread_handle_from_id (pthread_self ());
104                 if (handle == NULL) {
105                         SetLastError (ERROR_INVALID_HANDLE);
106                         return(WAIT_FAILED);
107                 }
108         }
109         
110         if (_wapi_handle_test_capabilities (handle,
111                                             WAPI_HANDLE_CAP_WAIT) == FALSE) {
112 #ifdef DEBUG
113                 g_message ("%s: handle %p can't be waited for", __func__,
114                            handle);
115 #endif
116
117                 return(WAIT_FAILED);
118         }
119
120         _wapi_handle_ops_prewait (handle);
121         
122         if (_wapi_handle_test_capabilities (handle, WAPI_HANDLE_CAP_SPECIAL_WAIT) == TRUE) {
123 #ifdef DEBUG
124                 g_message ("%s: handle %p has special wait", __func__, handle);
125 #endif
126
127                 ret = _wapi_handle_ops_special_wait (handle, timeout);
128         
129                 if (alertable && _wapi_thread_apc_pending (current_thread)) {
130                         apc_pending = TRUE;
131                         ret = WAIT_IO_COMPLETION;
132                 }
133
134                 goto check_pending;
135         }
136         
137         
138 #ifdef DEBUG
139         g_message ("%s: locking handle %p", __func__, handle);
140 #endif
141
142         pthread_cleanup_push ((void(*)(void *))_wapi_handle_unlock_handle,
143                               handle);
144         thr_ret = _wapi_handle_lock_handle (handle);
145         g_assert (thr_ret == 0);
146
147         if (_wapi_handle_test_capabilities (handle,
148                                             WAPI_HANDLE_CAP_OWN) == TRUE) {
149                 if (own_if_owned (handle) == TRUE) {
150 #ifdef DEBUG
151                         g_message ("%s: handle %p already owned", __func__,
152                                    handle);
153 #endif
154                         ret = WAIT_OBJECT_0;
155                         goto done;
156                 }
157         }
158         
159         if (alertable && _wapi_thread_apc_pending (current_thread)) {
160                 apc_pending = TRUE;
161                 ret = WAIT_IO_COMPLETION;
162                 goto done;
163         }
164         
165         if (own_if_signalled (handle) == TRUE) {
166 #ifdef DEBUG
167                 g_message ("%s: handle %p already signalled", __func__,
168                            handle);
169 #endif
170
171                 ret=WAIT_OBJECT_0;
172                 goto done;
173         }
174
175         if (timeout == 0) {
176                 ret = WAIT_TIMEOUT;
177                 goto done;
178         }
179         /* Have to wait for it */
180         if (timeout != INFINITE) {
181                 _wapi_calc_timeout (&abstime, timeout);
182         }
183         
184         do {
185                 /* Check before waiting on the condition, just in case
186                  */
187                 _wapi_handle_ops_prewait (handle);
188
189                 if (own_if_signalled (handle)) {
190 #ifdef DEBUG
191                         g_message ("%s: handle %p signalled", __func__,
192                                    handle);
193 #endif
194
195                         ret = WAIT_OBJECT_0;
196                         goto done;
197                 }
198                         
199                 if (timeout == INFINITE) {
200                         waited = _wapi_handle_wait_signal_handle (handle, alertable);
201                 } else {
202                         waited = _wapi_handle_timedwait_signal_handle (handle, &abstime, alertable);
203                 }
204         
205                 if (alertable)
206                         apc_pending = _wapi_thread_apc_pending (current_thread);
207
208                 if(waited==0 && !apc_pending) {
209                         /* Condition was signalled, so hopefully
210                          * handle is signalled now.  (It might not be
211                          * if someone else got in before us.)
212                          */
213                         if (own_if_signalled (handle)) {
214 #ifdef DEBUG
215                                 g_message ("%s: handle %p signalled", __func__,
216                                            handle);
217 #endif
218
219                                 ret=WAIT_OBJECT_0;
220                                 goto done;
221                         }
222                 
223                         /* Better luck next time */
224                 }
225         } while(waited == 0 && !apc_pending);
226
227         /* Timeout or other error */
228 #ifdef DEBUG
229         g_message ("%s: wait on handle %p error: %s", __func__, handle,
230                    strerror (waited));
231 #endif
232
233         ret = WAIT_TIMEOUT;
234         
235 done:
236
237 #ifdef DEBUG
238         g_message ("%s: unlocking handle %p", __func__, handle);
239 #endif
240         
241         thr_ret = _wapi_handle_unlock_handle (handle);
242         g_assert (thr_ret == 0);
243         pthread_cleanup_pop (0);
244         
245 check_pending:
246         if (apc_pending) {
247                 _wapi_thread_dispatch_apc_queue (current_thread);
248                 ret = WAIT_IO_COMPLETION;
249         }
250                 
251         return(ret);
252 }
253
254 guint32 WaitForSingleObject(gpointer handle, guint32 timeout)
255 {
256         return WaitForSingleObjectEx (handle, timeout, FALSE);
257 }
258
259
260 /**
261  * SignalObjectAndWait:
262  * @signal_handle: An object to signal
263  * @wait: An object to wait for
264  * @timeout: The maximum time in milliseconds to wait for
265  * @alertable: Specifies whether the function returnes when the system
266  * queues an I/O completion routine or an APC for the calling thread.
267  *
268  * Atomically signals @signal and waits for @wait to become signalled,
269  * or @timeout ms elapses.  If @timeout is zero, the object's state is
270  * tested and the function returns immediately.  If @timeout is
271  * %INFINITE, the function waits forever.
272  *
273  * @signal can be a semaphore, mutex or event object.
274  *
275  * If @alertable is %TRUE and the system queues an I/O completion
276  * routine or an APC for the calling thread, the function returns and
277  * the thread calls the completion routine or APC function.  If
278  * %FALSE, the function does not return, and the thread does not call
279  * the completion routine or APC function.  A completion routine is
280  * queued when the ReadFileEx() or WriteFileEx() function in which it
281  * was specified has completed.  The calling thread is the thread that
282  * initiated the read or write operation.  An APC is queued when
283  * QueueUserAPC() is called.  Currently completion routines and APC
284  * functions are not supported.
285  *
286  * Return value: %WAIT_ABANDONED - @wait is a mutex that was not
287  * released by the owning thread when it exited.  Ownershop of the
288  * mutex object is granted to the calling thread and the mutex is set
289  * to nonsignalled.  %WAIT_IO_COMPLETION - the wait was ended by one
290  * or more user-mode asynchronous procedure calls queued to the
291  * thread.  %WAIT_OBJECT_0 - The state of @wait is signalled.
292  * %WAIT_TIMEOUT - The @timeout interval elapsed and @wait's state is
293  * still not signalled.  %WAIT_FAILED - an error occurred.
294  */
295 guint32 SignalObjectAndWait(gpointer signal_handle, gpointer wait,
296                             guint32 timeout, gboolean alertable)
297 {
298         guint32 ret, waited;
299         struct timespec abstime;
300         int thr_ret;
301         gboolean apc_pending = FALSE;
302         gpointer current_thread = _wapi_thread_handle_from_id (pthread_self ());
303         
304         if (current_thread == NULL) {
305                 SetLastError (ERROR_INVALID_HANDLE);
306                 return(WAIT_FAILED);
307         }
308
309         if (signal_handle == _WAPI_THREAD_CURRENT) {
310                 signal_handle = _wapi_thread_handle_from_id (pthread_self ());
311                 if (signal_handle == NULL) {
312                         SetLastError (ERROR_INVALID_HANDLE);
313                         return(WAIT_FAILED);
314                 }
315         }
316
317         if (wait == _WAPI_THREAD_CURRENT) {
318                 wait = _wapi_thread_handle_from_id (pthread_self ());
319                 if (wait == NULL) {
320                         SetLastError (ERROR_INVALID_HANDLE);
321                         return(WAIT_FAILED);
322                 }
323         }
324         
325         if (_wapi_handle_test_capabilities (signal_handle,
326                                             WAPI_HANDLE_CAP_SIGNAL)==FALSE) {
327                 return(WAIT_FAILED);
328         }
329         
330         if (_wapi_handle_test_capabilities (wait,
331                                             WAPI_HANDLE_CAP_WAIT)==FALSE) {
332                 return(WAIT_FAILED);
333         }
334
335         _wapi_handle_ops_prewait (wait);
336         
337         if (_wapi_handle_test_capabilities (wait, WAPI_HANDLE_CAP_SPECIAL_WAIT) == TRUE) {
338                 g_warning ("%s: handle %p has special wait, implement me!!",
339                            __func__, wait);
340
341                 return (WAIT_FAILED);
342         }
343
344 #ifdef DEBUG
345         g_message ("%s: locking handle %p", __func__, wait);
346 #endif
347
348         pthread_cleanup_push ((void(*)(void *))_wapi_handle_unlock_handle,
349                               wait);
350         thr_ret = _wapi_handle_lock_handle (wait);
351         g_assert (thr_ret == 0);
352
353         _wapi_handle_ops_signal (signal_handle);
354
355         if (_wapi_handle_test_capabilities (wait, WAPI_HANDLE_CAP_OWN)==TRUE) {
356                 if (own_if_owned (wait)) {
357 #ifdef DEBUG
358                         g_message ("%s: handle %p already owned", __func__,
359                                    wait);
360 #endif
361                         ret = WAIT_OBJECT_0;
362                         goto done;
363                 }
364         }
365         
366         if (alertable && _wapi_thread_apc_pending (current_thread)) {
367                 apc_pending = TRUE;
368                 ret = WAIT_IO_COMPLETION;
369                 goto done;
370         }
371         
372         if (own_if_signalled (wait)) {
373 #ifdef DEBUG
374                 g_message ("%s: handle %p already signalled", __func__, wait);
375 #endif
376
377                 ret = WAIT_OBJECT_0;
378                 goto done;
379         }
380
381         /* Have to wait for it */
382         if (timeout != INFINITE) {
383                 _wapi_calc_timeout (&abstime, timeout);
384         }
385         
386         do {
387                 /* Check before waiting on the condition, just in case
388                  */
389                 _wapi_handle_ops_prewait (wait);
390         
391                 if (own_if_signalled (wait)) {
392 #ifdef DEBUG
393                         g_message ("%s: handle %p signalled", __func__, wait);
394 #endif
395
396                         ret = WAIT_OBJECT_0;
397                         goto done;
398                 }
399                 
400                 if (timeout == INFINITE) {
401                         waited = _wapi_handle_wait_signal_handle (wait, alertable);
402                 } else {
403                         waited = _wapi_handle_timedwait_signal_handle (wait, &abstime, alertable);
404                 }
405
406                 if (alertable) {
407                         apc_pending = _wapi_thread_apc_pending (current_thread);
408                 }
409
410                 if (waited==0 && !apc_pending) {
411                         /* Condition was signalled, so hopefully
412                          * handle is signalled now.  (It might not be
413                          * if someone else got in before us.)
414                          */
415                         if (own_if_signalled (wait)) {
416 #ifdef DEBUG
417                                 g_message ("%s: handle %p signalled", __func__,
418                                            wait);
419 #endif
420
421                                 ret = WAIT_OBJECT_0;
422                                 goto done;
423                         }
424                 
425                         /* Better luck next time */
426                 }
427         } while(waited == 0 && !apc_pending);
428
429         /* Timeout or other error */
430 #ifdef DEBUG
431         g_message ("%s: wait on handle %p error: %s", __func__, wait,
432                    strerror (ret));
433 #endif
434
435         ret = WAIT_TIMEOUT;
436         
437 done:
438
439 #ifdef DEBUG
440         g_message ("%s: unlocking handle %p", __func__, wait);
441 #endif
442
443         thr_ret = _wapi_handle_unlock_handle (wait);
444         g_assert (thr_ret == 0);
445         pthread_cleanup_pop (0);
446
447         if (apc_pending) {
448                 _wapi_thread_dispatch_apc_queue (current_thread);
449                 ret = WAIT_IO_COMPLETION;
450         }
451         
452         return(ret);
453 }
454
455 struct handle_cleanup_data
456 {
457         guint32 numobjects;
458         gpointer *handles;
459 };
460
461 static void handle_cleanup (void *data)
462 {
463         struct handle_cleanup_data *handles = (struct handle_cleanup_data *)data;
464
465         _wapi_handle_unlock_handles (handles->numobjects, handles->handles);
466 }
467
468 static gboolean test_and_own (guint32 numobjects, gpointer *handles,
469                               gboolean waitall, guint32 *count,
470                               guint32 *lowest)
471 {
472         struct handle_cleanup_data cleanup_data;
473         gboolean done;
474         int i;
475         
476 #ifdef DEBUG
477         g_message ("%s: locking handles", __func__);
478 #endif
479         cleanup_data.numobjects = numobjects;
480         cleanup_data.handles = handles;
481         
482         pthread_cleanup_push (handle_cleanup, (void *)&cleanup_data);
483         done = _wapi_handle_count_signalled_handles (numobjects, handles,
484                                                      waitall, count, lowest);
485         if (done == TRUE) {
486                 if (waitall == TRUE) {
487                         for (i = 0; i < numobjects; i++) {
488                                 own_if_signalled (handles[i]);
489                         }
490                 } else {
491                         own_if_signalled (handles[*lowest]);
492                 }
493         }
494         
495 #ifdef DEBUG
496         g_message ("%s: unlocking handles", __func__);
497 #endif
498
499         /* calls the unlock function */
500         pthread_cleanup_pop (1);
501
502         return(done);
503 }
504
505
506
507 /**
508  * WaitForMultipleObjectsEx:
509  * @numobjects: The number of objects in @handles. The maximum allowed
510  * is %MAXIMUM_WAIT_OBJECTS.
511  * @handles: An array of object handles.  Duplicates are not allowed.
512  * @waitall: If %TRUE, this function waits until all of the handles
513  * are signalled.  If %FALSE, this function returns when any object is
514  * signalled.
515  * @timeout: The maximum time in milliseconds to wait for.
516  * @alertable: if TRUE, the wait can be interrupted by an APC call
517  * 
518  * This function returns when either one or more of @handles is
519  * signalled, or @timeout ms elapses.  If @timeout is zero, the state
520  * of each item of @handles is tested and the function returns
521  * immediately.  If @timeout is %INFINITE, the function waits forever.
522  *
523  * Return value: %WAIT_OBJECT_0 to %WAIT_OBJECT_0 + @numobjects - 1 -
524  * if @waitall is %TRUE, indicates that all objects are signalled.  If
525  * @waitall is %FALSE, the return value minus %WAIT_OBJECT_0 indicates
526  * the first index into @handles of the objects that are signalled.
527  * %WAIT_ABANDONED_0 to %WAIT_ABANDONED_0 + @numobjects - 1 - if
528  * @waitall is %TRUE, indicates that all objects are signalled, and at
529  * least one object is an abandoned mutex object (See
530  * WaitForSingleObject() for a description of abandoned mutexes.)  If
531  * @waitall is %FALSE, the return value minus %WAIT_ABANDONED_0
532  * indicates the first index into @handles of an abandoned mutex.
533  * %WAIT_TIMEOUT - The @timeout interval elapsed and no objects in
534  * @handles are signalled.  %WAIT_FAILED - an error occurred.
535  * %WAIT_IO_COMPLETION - the wait was ended by an APC.
536  */
537 guint32 WaitForMultipleObjectsEx(guint32 numobjects, gpointer *handles,
538                                  gboolean waitall, guint32 timeout,
539                                  gboolean alertable)
540 {
541         GHashTable *dups;
542         gboolean duplicate = FALSE, bogustype = FALSE, done;
543         guint32 count, lowest;
544         struct timespec abstime;
545         guint i;
546         guint32 ret;
547         int thr_ret;
548         gpointer current_thread = _wapi_thread_handle_from_id (pthread_self ());
549         
550         if (current_thread == NULL) {
551                 SetLastError (ERROR_INVALID_HANDLE);
552                 return(WAIT_FAILED);
553         }
554         
555         if (numobjects > MAXIMUM_WAIT_OBJECTS) {
556 #ifdef DEBUG
557                 g_message ("%s: Too many handles: %d", __func__, numobjects);
558 #endif
559
560                 return(WAIT_FAILED);
561         }
562         
563         if (numobjects == 1) {
564                 return WaitForSingleObjectEx (handles [0], timeout, alertable);
565         }
566
567         /* Check for duplicates */
568         dups = g_hash_table_new (g_direct_hash, g_direct_equal);
569         for (i = 0; i < numobjects; i++) {
570                 gpointer exists;
571
572                 if (handles[i] == _WAPI_THREAD_CURRENT) {
573                         handles[i] = _wapi_thread_handle_from_id (pthread_self ());
574                         
575                         if (handles[i] == NULL) {
576 #ifdef DEBUG
577                                 g_message ("%s: Handle %d bogus", __func__, i);
578 #endif
579
580                                 bogustype = TRUE;
581                                 break;
582                         }
583                 }
584                 
585                 exists = g_hash_table_lookup (dups, handles[i]);
586                 if (exists != NULL) {
587 #ifdef DEBUG
588                         g_message ("%s: Handle %p duplicated", __func__,
589                                    handles[i]);
590 #endif
591
592                         duplicate = TRUE;
593                         break;
594                 }
595
596                 if (_wapi_handle_test_capabilities (handles[i], WAPI_HANDLE_CAP_WAIT) == FALSE) {
597 #ifdef DEBUG
598                         g_message ("%s: Handle %p can't be waited for",
599                                    __func__, handles[i]);
600 #endif
601
602                         bogustype = TRUE;
603                 }
604
605                 g_hash_table_insert (dups, handles[i], handles[i]);
606                 _wapi_handle_ops_prewait (handles[i]);
607         }
608         g_hash_table_destroy (dups);
609
610         if (duplicate == TRUE) {
611 #ifdef DEBUG
612                 g_message ("%s: Returning due to duplicates", __func__);
613 #endif
614
615                 return(WAIT_FAILED);
616         }
617
618         if (bogustype == TRUE) {
619 #ifdef DEBUG
620                 g_message ("%s: Returning due to bogus type", __func__);
621 #endif
622
623                 return(WAIT_FAILED);
624         }
625
626         done = test_and_own (numobjects, handles, waitall, &count, &lowest);
627         if (done == TRUE) {
628                 return(WAIT_OBJECT_0+lowest);
629         }
630         
631         if (timeout == 0) {
632                 return WAIT_TIMEOUT;
633         }
634         /* Have to wait for some or all handles to become signalled
635          */
636
637         if(timeout!=INFINITE) {
638                 _wapi_calc_timeout (&abstime, timeout);
639         }
640
641         if (alertable && _wapi_thread_apc_pending (current_thread)) {
642                 _wapi_thread_dispatch_apc_queue (current_thread);
643                 return WAIT_IO_COMPLETION;
644         }
645         
646         while(1) {
647                 /* Prod all handles with prewait methods and
648                  * special-wait handles that aren't already signalled
649                  */
650                 for (i = 0; i < numobjects; i++) {
651                         _wapi_handle_ops_prewait (handles[i]);
652                 
653                         if (_wapi_handle_test_capabilities (handles[i], WAPI_HANDLE_CAP_SPECIAL_WAIT) == TRUE && _wapi_handle_issignalled (handles[i]) == FALSE) {
654                                 _wapi_handle_ops_special_wait (handles[i], 0);
655                         }
656                 }
657
658                 /* Check before waiting on the condition, just in case
659                  */
660                 done = test_and_own (numobjects, handles, waitall,
661                                      &count, &lowest);
662                 if (done == TRUE) {
663                         return(WAIT_OBJECT_0 + lowest);
664                 }
665                 
666 #ifdef DEBUG
667                 g_message ("%s: locking signal mutex", __func__);
668 #endif
669
670                 pthread_cleanup_push ((void(*)(void *))_wapi_handle_unlock_signal_mutex, NULL);
671                 thr_ret = _wapi_handle_lock_signal_mutex ();
672                 g_assert (thr_ret == 0);
673                 
674                 if (timeout == INFINITE) {
675                         ret = _wapi_handle_wait_signal ();
676                 } else {
677                         ret = _wapi_handle_timedwait_signal (&abstime);
678                 }
679
680 #ifdef DEBUG
681                 g_message ("%s: unlocking signal mutex", __func__);
682 #endif
683
684                 thr_ret = _wapi_handle_unlock_signal_mutex (NULL);
685                 g_assert (thr_ret == 0);
686                 pthread_cleanup_pop (0);
687                 
688                 if (alertable && _wapi_thread_apc_pending (current_thread)) {
689                         _wapi_thread_dispatch_apc_queue (current_thread);
690                         return WAIT_IO_COMPLETION;
691                 }
692         
693                 /* Check if everything is signalled, as we can't
694                  * guarantee to notice a shared signal even if the
695                  * wait timed out
696                  */
697                 done = test_and_own (numobjects, handles, waitall,
698                                      &count, &lowest);
699                 if (done == TRUE) {
700                         return(WAIT_OBJECT_0+lowest);
701                 } else if (ret != 0) {
702                         /* Didn't get all handles, and there was a
703                          * timeout or other error
704                          */
705 #ifdef DEBUG
706                         g_message ("%s: wait returned error: %s", __func__,
707                                    strerror (ret));
708 #endif
709
710                         if(ret==ETIMEDOUT) {
711                                 return(WAIT_TIMEOUT);
712                         } else {
713                                 return(WAIT_FAILED);
714                         }
715                 }
716         }
717 }
718
719 guint32 WaitForMultipleObjects(guint32 numobjects, gpointer *handles,
720                                gboolean waitall, guint32 timeout)
721 {
722         return WaitForMultipleObjectsEx(numobjects, handles, waitall, timeout, FALSE);
723 }