Mon Mar 11 11:12:23 CET 2002 Paolo Molaro <lupus@ximian.com>
[mono.git] / mono / metadata / threads.c
1 /*
2  * threads.c: Thread support internal calls
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2001 Ximian, Inc.
8  */
9
10 #include <config.h>
11 #include <glib.h>
12
13 #include <mono/metadata/object.h>
14 #include <mono/metadata/appdomain.h>
15 #include <mono/metadata/threads.h>
16 #include <mono/metadata/threads-types.h>
17 #include <mono/io-layer/io-layer.h>
18
19 #undef THREAD_DEBUG
20 #undef THREAD_LOCK_DEBUG
21 #undef THREAD_WAIT_DEBUG
22
23 struct StartInfo 
24 {
25         guint32 (*func)(void *);
26         MonoObject *obj;
27         MonoDomain *domain;
28 };
29
30 /* Controls access to the 'threads' array */
31 static CRITICAL_SECTION threads_mutex;
32
33 /* Controls access to the sync field in MonoObjects, to avoid race
34  * conditions when adding sync data to an object for the first time.
35  */
36 static CRITICAL_SECTION monitor_mutex;
37
38 /* The array of existing threads that need joining before exit */
39 static GPtrArray *threads=NULL;
40
41 /* The MonoObject associated with the main thread */
42 static MonoObject *main_thread;
43
44 /* The TLS key that holds the MonoObject assigned to each thread */
45 static guint32 current_object_key;
46
47 /* The TLS key that holds the LocalDataStoreSlot hash in each thread */
48 static guint32 slothash_key;
49
50 static guint32 start_wrapper(void *data)
51 {
52         struct StartInfo *start_info=(struct StartInfo *)data;
53         guint32 (*start_func)(void *);
54         
55 #ifdef THREAD_DEBUG
56         g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper");
57 #endif
58
59         /* FIXME: GC problem here with recorded object
60          * pointer!
61          *
62          * This is recorded so CurrentThread can return the
63          * Thread object.
64          */
65         TlsSetValue(current_object_key, start_info->obj);
66         start_func=start_info->func;
67         mono_domain_set (start_info->domain);
68         
69         g_free(start_info);
70         
71         start_func(NULL);
72
73 #ifdef THREAD_DEBUG
74         g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper terminating");
75 #endif
76
77         return(0);
78 }
79                 
80 static void handle_store(HANDLE thread)
81 {
82 #ifdef THREAD_DEBUG
83         g_message(G_GNUC_PRETTY_FUNCTION ": thread %p", thread);
84 #endif
85
86         EnterCriticalSection(&threads_mutex);
87         if(threads==NULL) {
88                 threads=g_ptr_array_new();
89         }
90         g_ptr_array_add(threads, thread);
91         LeaveCriticalSection(&threads_mutex);
92 }
93
94 static void handle_remove(HANDLE thread)
95 {
96 #ifdef THREAD_DEBUG
97         g_message(G_GNUC_PRETTY_FUNCTION ": thread %p", thread);
98 #endif
99
100         EnterCriticalSection(&threads_mutex);
101         g_ptr_array_remove_fast(threads, thread);
102         LeaveCriticalSection(&threads_mutex);
103         CloseHandle(thread);
104 }
105
106
107 HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this,
108                                                          MonoObject *start)
109 {
110         MonoClassField *field;
111         guint32 (*start_func)(void *);
112         struct StartInfo *start_info;
113         HANDLE thread;
114         guint32 tid;
115         
116 #ifdef THREAD_DEBUG
117         g_message(G_GNUC_PRETTY_FUNCTION
118                   ": Trying to start a new thread: this (%p) start (%p)",
119                   this, start);
120 #endif
121         
122         field=mono_class_get_field_from_name(mono_defaults.delegate_class, "method_ptr");
123         start_func= *(gpointer *)(((char *)start) + field->offset);
124         
125         if(start_func==NULL) {
126                 g_warning(G_GNUC_PRETTY_FUNCTION
127                           ": Can't locate start method!");
128                 return(NULL);
129         } else {
130                 /* This is freed in start_wrapper */
131                 start_info=g_new0(struct StartInfo, 1);
132                 start_info->func=start_func;
133                 start_info->obj=this;
134                 start_info->domain = mono_domain_get ();
135                 
136                 thread=CreateThread(NULL, 0, start_wrapper, start_info,
137                                     CREATE_SUSPENDED, &tid);
138                 if(thread==NULL) {
139                         g_warning(G_GNUC_PRETTY_FUNCTION
140                                   ": CreateThread error 0x%x", GetLastError());
141                         return(NULL);
142                 }
143                 
144 #ifdef THREAD_DEBUG
145                 g_message(G_GNUC_PRETTY_FUNCTION ": Started thread ID %d",
146                           tid);
147 #endif
148
149                 /* Store handle for cleanup later */
150                 handle_store(thread);
151                 
152                 return(thread);
153         }
154 }
155
156 void ves_icall_System_Threading_Thread_Start_internal(MonoObject *this,
157                                                       HANDLE thread)
158 {
159 #ifdef THREAD_DEBUG
160         g_message(G_GNUC_PRETTY_FUNCTION ": Launching thread %p", this);
161 #endif
162
163         ResumeThread(thread);
164 }
165
166 void ves_icall_System_Threading_Thread_Sleep_internal(gint32 ms)
167 {
168 #ifdef THREAD_DEBUG
169         g_message(G_GNUC_PRETTY_FUNCTION ": Sleeping for %d ms", ms);
170 #endif
171
172         Sleep(ms);
173 }
174
175 MonoObject *ves_icall_System_Threading_Thread_CurrentThread_internal(void)
176 {
177         MonoObject *thread;
178         
179         /* Find the current thread object */
180         thread=TlsGetValue(current_object_key);
181         return(thread);
182 }
183
184 gboolean ves_icall_System_Threading_Thread_Join_internal(MonoObject *this,
185                                                          int ms, HANDLE thread)
186 {
187         gboolean ret;
188         
189         if(ms== -1) {
190                 ms=INFINITE;
191         }
192         
193         ret=WaitForSingleObject(thread, ms);
194         if(ret==WAIT_OBJECT_0) {
195                 /* Clean up the handle */
196                 handle_remove(thread);
197                 return(TRUE);
198         }
199         
200         return(FALSE);
201 }
202
203 void ves_icall_System_Threading_Thread_SlotHash_store(MonoObject *data)
204 {
205 #ifdef THREAD_DEBUG
206         g_message(G_GNUC_PRETTY_FUNCTION ": Storing key %p", data);
207 #endif
208
209         /* Object location stored here */
210         TlsSetValue(slothash_key, data);
211 }
212
213 MonoObject *ves_icall_System_Threading_Thread_SlotHash_lookup(void)
214 {
215         MonoObject *data;
216
217         data=TlsGetValue(slothash_key);
218         
219 #ifdef THREAD_DEBUG
220         g_message(G_GNUC_PRETTY_FUNCTION ": Retrieved key %p", data);
221 #endif
222         
223         return(data);
224 }
225
226 static MonoThreadsSync *mon_new(void)
227 {
228         MonoThreadsSync *new;
229         
230         /* This should be freed when the object that owns it is
231          * deleted
232          */
233
234         new=(MonoThreadsSync *)g_new0(MonoThreadsSync, 1);
235         new->monitor=CreateMutex(NULL, FALSE, NULL);
236 #ifdef THREAD_LOCK_DEBUG
237         g_message(G_GNUC_PRETTY_FUNCTION ": ThreadsSync mutex created: %p",
238                   new->monitor);
239 #endif
240
241         new->waiters_count=0;
242         new->was_broadcast=FALSE;
243         InitializeCriticalSection(&new->waiters_count_lock);
244         new->sema=CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
245         new->waiters_done=CreateEvent(NULL, FALSE, FALSE, NULL);
246         
247         return(new);
248 }
249
250 gboolean ves_icall_System_Threading_Monitor_Monitor_try_enter(MonoObject *obj,
251                                                               int ms)
252 {
253         MonoThreadsSync *mon;
254         guint32 ret;
255         
256 #ifdef THREAD_LOCK_DEBUG
257         g_message(G_GNUC_PRETTY_FUNCTION
258                   ": Trying to lock %p in thread %d", obj,
259                   GetCurrentThreadId());
260 #endif
261
262         EnterCriticalSection(&monitor_mutex);
263
264         mon=obj->synchronisation;
265         if(mon==NULL) {
266                 mon=mon_new();
267                 obj->synchronisation=mon;
268         }
269         
270         /* Don't hold the monitor lock while waiting to acquire the
271          * object lock
272          */
273         LeaveCriticalSection(&monitor_mutex);
274         
275         /* Acquire the mutex */
276 #ifdef THREAD_LOCK_DEBUG
277         g_message(G_GNUC_PRETTY_FUNCTION ": Acquiring monitor mutex %p",
278                   mon->monitor);
279 #endif
280         ret=WaitForSingleObject(mon->monitor, ms);
281         if(ret==WAIT_OBJECT_0) {
282                 mon->count++;
283                 mon->tid=GetCurrentThreadId();
284         
285 #ifdef THREAD_LOCK_DEBUG
286         g_message(G_GNUC_PRETTY_FUNCTION ": %p now locked %d times", obj,
287                   mon->count);
288 #endif
289
290                 return(TRUE);
291         }
292
293         return(FALSE);
294 }
295
296 void ves_icall_System_Threading_Monitor_Monitor_exit(MonoObject *obj)
297 {
298         MonoThreadsSync *mon;
299         
300 #ifdef THREAD_LOCK_DEBUG
301         g_message(G_GNUC_PRETTY_FUNCTION ": Unlocking %p in thread %d", obj,
302                   GetCurrentThreadId());
303 #endif
304
305         /* No need to lock monitor_mutex here because we only adjust
306          * the monitor state if this thread already owns the lock
307          */
308         mon=obj->synchronisation;
309
310         if(mon==NULL) {
311                 return;
312         }
313
314         if(mon->tid!=GetCurrentThreadId()) {
315                 return;
316         }
317         
318         mon->count--;
319         
320 #ifdef THREAD_LOCK_DEBUG
321         g_message(G_GNUC_PRETTY_FUNCTION ": %p now locked %d times", obj,
322                   mon->count);
323 #endif
324
325         if(mon->count==0) {
326                 mon->tid=0;     /* FIXME: check that 0 isnt a valid id */
327         }
328         
329 #ifdef THREAD_LOCK_DEBUG
330         g_message(G_GNUC_PRETTY_FUNCTION ": Releasing mutex %p", mon->monitor);
331 #endif
332
333         ReleaseMutex(mon->monitor);
334 }
335
336 gboolean ves_icall_System_Threading_Monitor_Monitor_test_owner(MonoObject *obj)
337 {
338         MonoThreadsSync *mon;
339         gboolean ret=FALSE;
340         
341 #ifdef THREAD_LOCK_DEBUG
342         g_message(G_GNUC_PRETTY_FUNCTION
343                   ": Testing if %p is owned by thread %d", obj,
344                   GetCurrentThreadId());
345 #endif
346
347         EnterCriticalSection(&monitor_mutex);
348         
349         mon=obj->synchronisation;
350         if(mon==NULL) {
351                 goto finished;
352         }
353
354         if(mon->tid!=GetCurrentThreadId()) {
355                 goto finished;
356         }
357         
358         ret=TRUE;
359         
360 finished:
361         LeaveCriticalSection(&monitor_mutex);
362
363         return(ret);
364 }
365
366 gboolean ves_icall_System_Threading_Monitor_Monitor_test_synchronised(MonoObject *obj)
367 {
368         MonoThreadsSync *mon;
369         gboolean ret=FALSE;
370         
371 #ifdef THREAD_LOCK_DEBUG
372         g_message(G_GNUC_PRETTY_FUNCTION
373                   ": Testing if %p is owned by any thread", obj);
374 #endif
375
376         EnterCriticalSection(&monitor_mutex);
377         
378         mon=obj->synchronisation;
379         if(mon==NULL) {
380                 goto finished;
381         }
382
383         if(mon->tid==0) {
384                 goto finished;
385         }
386         
387         g_assert(mon->count);
388         
389         ret=TRUE;
390         
391 finished:
392         LeaveCriticalSection(&monitor_mutex);
393
394         return(ret);
395 }
396
397         
398 void ves_icall_System_Threading_Monitor_Monitor_pulse(MonoObject *obj)
399 {
400         gboolean have_waiters;
401         MonoThreadsSync *mon;
402         
403 #ifdef THREAD_LOCK_DEBUG
404         g_message("Pulsing %p in thread %d", obj, GetCurrentThreadId());
405 #endif
406
407         EnterCriticalSection(&monitor_mutex);
408         
409         mon=obj->synchronisation;
410         if(mon==NULL) {
411                 LeaveCriticalSection(&monitor_mutex);
412                 return;
413         }
414
415         if(mon->tid!=GetCurrentThreadId()) {
416                 LeaveCriticalSection(&monitor_mutex);
417                 return;
418         }
419         LeaveCriticalSection(&monitor_mutex);
420         
421         EnterCriticalSection(&mon->waiters_count_lock);
422         have_waiters=(mon->waiters_count>0);
423         LeaveCriticalSection(&mon->waiters_count_lock);
424         
425         if(have_waiters==TRUE) {
426                 ReleaseSemaphore(mon->sema, 1, 0);
427         }
428 }
429
430 void ves_icall_System_Threading_Monitor_Monitor_pulse_all(MonoObject *obj)
431 {
432         gboolean have_waiters=FALSE;
433         MonoThreadsSync *mon;
434         
435 #ifdef THREAD_LOCK_DEBUG
436         g_message("Pulsing all %p", obj);
437 #endif
438
439         EnterCriticalSection(&monitor_mutex);
440         
441         mon=obj->synchronisation;
442         if(mon==NULL) {
443                 LeaveCriticalSection(&monitor_mutex);
444                 return;
445         }
446
447         if(mon->tid!=GetCurrentThreadId()) {
448                 LeaveCriticalSection(&monitor_mutex);
449                 return;
450         }
451         LeaveCriticalSection(&monitor_mutex);
452         
453         EnterCriticalSection(&mon->waiters_count_lock);
454         if(mon->waiters_count>0) {
455                 mon->was_broadcast=TRUE;
456                 have_waiters=TRUE;
457         }
458         
459         if(have_waiters==TRUE) {
460                 ReleaseSemaphore(mon->sema, mon->waiters_count, 0);
461                 
462                 LeaveCriticalSection(&mon->waiters_count_lock);
463                 
464                 WaitForSingleObject(mon->waiters_done, INFINITE);
465                 mon->was_broadcast=FALSE;
466         } else {
467                 LeaveCriticalSection(&mon->waiters_count_lock);
468         }
469 }
470
471 gboolean ves_icall_System_Threading_Monitor_Monitor_wait(MonoObject *obj,
472                                                          int ms)
473 {
474         gboolean last_waiter;
475         MonoThreadsSync *mon;
476         guint32 save_count;
477         
478 #ifdef THREAD_LOCK_DEBUG
479         g_message("Trying to wait for %p in thread %d with timeout %dms", obj,
480                   GetCurrentThreadId(), ms);
481 #endif
482
483         EnterCriticalSection(&monitor_mutex);
484         
485         mon=obj->synchronisation;
486         if(mon==NULL) {
487                 LeaveCriticalSection(&monitor_mutex);
488                 return(FALSE);
489         }
490
491         if(mon->tid!=GetCurrentThreadId()) {
492                 LeaveCriticalSection(&monitor_mutex);
493                 return(FALSE);
494         }
495         LeaveCriticalSection(&monitor_mutex);
496         
497         EnterCriticalSection(&mon->waiters_count_lock);
498         mon->waiters_count++;
499         LeaveCriticalSection(&mon->waiters_count_lock);
500         
501 #ifdef THREAD_LOCK_DEBUG
502         g_message(G_GNUC_PRETTY_FUNCTION ": %p locked %d times", obj,
503                   mon->count);
504 #endif
505
506         /* We need to put the lock count back afterwards */
507         save_count=mon->count;
508         
509         while(mon->count>1) {
510 #ifdef THREAD_LOCK_DEBUG
511                 g_message(G_GNUC_PRETTY_FUNCTION ": Releasing mutex %p",
512                           mon->monitor);
513 #endif
514
515                 ReleaseMutex(mon->monitor);
516                 mon->count--;
517         }
518         
519         /* We're releasing this mutex */
520         mon->count=0;
521         mon->tid=0;
522 #ifdef THREAD_LOCK_DEBUG
523         g_message(G_GNUC_PRETTY_FUNCTION ": Signalling monitor mutex %p",
524                   mon->monitor);
525 #endif
526
527         SignalObjectAndWait(mon->monitor, mon->sema, INFINITE, FALSE);
528         
529         EnterCriticalSection(&mon->waiters_count_lock);
530         mon->waiters_count++;
531         last_waiter=mon->was_broadcast && mon->waiters_count==0;
532         LeaveCriticalSection(&mon->waiters_count_lock);
533         
534         if(last_waiter) {
535 #ifdef THREAD_LOCK_DEBUG
536         g_message(G_GNUC_PRETTY_FUNCTION ": Waiting for monitor mutex %p",
537                   mon->monitor);
538 #endif
539                 SignalObjectAndWait(mon->waiters_done, mon->monitor, INFINITE, FALSE);
540         } else {
541 #ifdef THREAD_LOCK_DEBUG
542         g_message(G_GNUC_PRETTY_FUNCTION ": Waiting for monitor mutex %p",
543                   mon->monitor);
544 #endif
545                 WaitForSingleObject(mon->monitor, INFINITE);
546         }
547
548         /* We've reclaimed this mutex */
549         mon->count=save_count;
550         mon->tid=GetCurrentThreadId();
551
552         /* Lock the mutex the required number of times */
553         while(save_count>1) {
554 #ifdef THREAD_LOCK_DEBUG
555                 g_message(G_GNUC_PRETTY_FUNCTION
556                           ": Waiting for monitor mutex %p", mon->monitor);
557 #endif
558                 WaitForSingleObject(mon->monitor, INFINITE);
559                 save_count--;
560         }
561         
562 #ifdef THREAD_LOCK_DEBUG
563         g_message(G_GNUC_PRETTY_FUNCTION ": %p still locked %d times", obj,
564                   mon->count);
565 #endif
566         
567         return(TRUE);
568 }
569
570 /* FIXME: exitContext isnt documented */
571 gboolean ves_icall_System_Threading_WaitHandle_WaitAll_internal(MonoArray *mono_handles, gint32 ms, gboolean exitContext)
572 {
573         HANDLE *handles;
574         guint32 numhandles;
575         guint32 ret;
576         guint32 i;
577         
578         numhandles=mono_array_length(mono_handles);
579         handles=g_new0(HANDLE, numhandles);
580         for(i=0; i<numhandles; i++) {
581                 handles[i]=mono_array_get(mono_handles, HANDLE, i);
582         }
583         
584         if(ms== -1) {
585                 ms=INFINITE;
586         }
587         
588         ret=WaitForMultipleObjects(numhandles, handles, TRUE, ms);
589
590         g_free(handles);
591         
592         if(ret==WAIT_FAILED) {
593 #ifdef THREAD_WAIT_DEBUG
594                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait failed");
595 #endif
596                 return(FALSE);
597         } else if(ret==WAIT_TIMEOUT) {
598 #ifdef THREAD_WAIT_DEBUG
599                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait timed out");
600 #endif
601                 return(FALSE);
602         }
603         
604         return(TRUE);
605 }
606
607 /* FIXME: exitContext isnt documented */
608 gint32 ves_icall_System_Threading_WaitHandle_WaitAny_internal(MonoArray *mono_handles, gint32 ms, gboolean exitContext)
609 {
610         HANDLE *handles;
611         guint32 numhandles;
612         guint32 ret;
613         guint32 i;
614         
615         numhandles=mono_array_length(mono_handles);
616         handles=g_new0(HANDLE, numhandles);
617         for(i=0; i<numhandles; i++) {
618                 handles[i]=mono_array_get(mono_handles, HANDLE, i);
619         }
620         
621         if(ms== -1) {
622                 ms=INFINITE;
623         }
624
625         ret=WaitForMultipleObjects(numhandles, handles, FALSE, ms);
626
627         g_free(handles);
628         
629 #ifdef THREAD_WAIT_DEBUG
630         g_message(G_GNUC_PRETTY_FUNCTION ": returning %d", ret);
631 #endif
632
633         return(ret);
634 }
635
636 /* FIXME: exitContext isnt documented */
637 gboolean ves_icall_System_Threading_WaitHandle_WaitOne_internal(MonoObject *this, HANDLE handle, gint32 ms, gboolean exitContext)
638 {
639         guint32 ret;
640         
641 #ifdef THREAD_WAIT_DEBUG
642         g_message(G_GNUC_PRETTY_FUNCTION ": waiting for %p", handle);
643 #endif
644         
645         if(ms== -1) {
646                 ms=INFINITE;
647         }
648         
649         ret=WaitForSingleObject(handle, ms);
650         if(ret==WAIT_FAILED) {
651 #ifdef THREAD_WAIT_DEBUG
652                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait failed");
653 #endif
654                 return(FALSE);
655         } else if(ret==WAIT_TIMEOUT) {
656 #ifdef THREAD_WAIT_DEBUG
657                 g_message(G_GNUC_PRETTY_FUNCTION ": Wait timed out");
658 #endif
659                 return(FALSE);
660         }
661         
662         return(TRUE);
663 }
664
665 HANDLE ves_icall_System_Threading_Mutex_CreateMutex_internal (MonoBoolean owned,char *name) {    
666    return(CreateMutex(NULL,owned,name));                         
667 }                                                                   
668
669 void ves_icall_System_Threading_Mutex_ReleaseMutex_internal (HANDLE handle ) { 
670         ReleaseMutex(handle);
671 }
672 \r
673 HANDLE ves_icall_System_Threading_Events_CreateEvent_internal (MonoBoolean manual,\r
674                                                                                                                           MonoBoolean initial,\r
675                                                                                                                           char *name) {\r
676         return (CreateEvent(NULL,manual,initial,name));\r
677 }\r
678 \r
679 gboolean ves_icall_System_Threading_Events_SetEvent_internal (HANDLE handle) {\r
680         return (SetEvent(handle));\r
681 }\r
682
683 gboolean ves_icall_System_Threading_Events_ResetEvent_internal (HANDLE handle) {\r
684         return (ResetEvent(handle));\r
685 }\r
686
687 void mono_thread_init(MonoDomain *domain)
688 {
689         MonoClass *thread_class;
690         
691         /* Build a System.Threading.Thread object instance to return
692          * for the main line's Thread.CurrentThread property.
693          */
694         thread_class=mono_class_from_name(mono_defaults.corlib, "System.Threading", "Thread");
695         
696         /* I wonder what happens if someone tries to destroy this
697          * object? In theory, I guess the whole program should act as
698          * though exit() were called :-)
699          */
700         main_thread = mono_object_new (domain, thread_class);
701
702         InitializeCriticalSection(&threads_mutex);
703         InitializeCriticalSection(&monitor_mutex);
704         
705         current_object_key=TlsAlloc();
706         TlsSetValue(current_object_key, main_thread);
707
708         slothash_key=TlsAlloc();
709 }
710
711 void mono_thread_cleanup(void)
712 {
713         HANDLE wait[MAXIMUM_WAIT_OBJECTS];
714         guint32 i, j;
715         
716         /* join each thread that's still running */
717 #ifdef THREAD_DEBUG
718         g_message("Joining each running thread...");
719 #endif
720         
721         if(threads==NULL) {
722 #ifdef THREAD_DEBUG
723                 g_message("No threads");
724 #endif
725                 return;
726         }
727         
728         /* This isnt the right way to do it.
729          *
730          * The first method call should be started in its own thread,
731          * and then the main thread should poll an event and wait for
732          * any terminated threads, until there are none left.
733          */
734 #ifdef THREAD_DEBUG
735         g_message("There are %d threads to join", threads->len);
736 #endif
737
738         for(i=0; i<threads->len; i+=MAXIMUM_WAIT_OBJECTS) {
739                 for(j=0; j<MAXIMUM_WAIT_OBJECTS && i+j<threads->len; j++) {
740 #ifdef THREAD_DEBUG
741                         g_message("Waiting for threads %d in slot %d", i+j, j);
742 #endif
743                         wait[j]=g_ptr_array_index(threads, i+j);
744                 }
745 #ifdef THREAD_DEBUG
746                 g_message("%d threads to wait for in this batch", j);
747 #endif
748
749                 WaitForMultipleObjects(j, wait, TRUE, INFINITE);
750         }
751         
752         g_ptr_array_free(threads, FALSE);
753         threads=NULL;
754 }