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