2 * threads.c: Thread support internal calls
5 * Dick Porter (dick@ximian.com)
6 * Patrik Torstensson (patrik.torstensson@labs2.com)
8 * (C) 2001 Ximian, Inc.
14 #include <mono/metadata/object.h>
15 #include <mono/metadata/appdomain.h>
16 #include <mono/metadata/profiler-private.h>
17 #include <mono/metadata/threads.h>
18 #include <mono/metadata/threads-types.h>
19 #include <mono/io-layer/io-layer.h>
26 #undef THREAD_LOCK_DEBUG
27 #undef THREAD_WAIT_DEBUG
31 guint32 (*func)(void *);
42 /* Controls access to the 'threads' array */
43 static CRITICAL_SECTION threads_mutex;
45 /* Controls access to the sync field in MonoObjects, to avoid race
46 * conditions when adding sync data to an object for the first time.
48 static CRITICAL_SECTION monitor_mutex;
50 /* The array of existing threads that need joining before exit */
51 static GArray *threads=NULL;
53 /* The MonoObject associated with the main thread */
54 static MonoObject *main_thread;
56 /* The TLS key that holds the MonoObject assigned to each thread */
57 static guint32 current_object_key;
59 /* function called at thread start */
60 static MonoThreadStartCB mono_thread_start_cb = NULL;
62 /* The TLS key that holds the LocalDataStoreSlot hash in each thread */
63 static guint32 slothash_key;
65 /* Spin lock for InterlockedXXX 64 bit functions */
66 static CRITICAL_SECTION interlocked_mutex;
68 /* handle_store() and handle_remove() manage the array of threads that
69 * still need to be waited for when the main thread exits.
71 static void handle_store(guint32 tid)
76 g_message(G_GNUC_PRETTY_FUNCTION ": thread ID %d", tid);
79 EnterCriticalSection(&threads_mutex);
81 threads=g_array_new(FALSE, FALSE, sizeof(guint32));
84 /* Make sure there is only one instance in the array. We
85 * store the thread both in the start_wrapper (in the
86 * subthread), and as soon as possible in the parent thread.
87 * This is to minimise the window in which the thread exists
88 * but we haven't recorded it.
90 for(idx=0; idx<threads->len; idx++) {
91 if(g_array_index (threads, guint32, idx)==tid) {
92 g_array_remove_index_fast (threads, idx);
96 g_array_append_val(threads, tid);
97 LeaveCriticalSection(&threads_mutex);
100 static void handle_remove(guint32 tid)
105 g_message(G_GNUC_PRETTY_FUNCTION ": thread ID %d", tid);
108 EnterCriticalSection(&threads_mutex);
110 for(idx=0; idx<threads->len; idx++) {
111 if(g_array_index (threads, guint32, idx)==tid) {
112 g_array_remove_index_fast(threads, idx);
116 LeaveCriticalSection(&threads_mutex);
118 /* Don't close the handle here, wait for the object finalizer
119 * to do it. Otherwise, the following race condition applies:
121 * 1) Thread exits (and handle_remove() closes the handle)
123 * 2) Some other handle is reassigned the same slot
125 * 3) Another thread tries to join the first thread, and
126 * blocks waiting for the reassigned handle to be signalled
127 * (which might never happen). This is possible, because the
128 * thread calling Join() still has a reference to the first
133 static guint32 start_wrapper(void *data)
135 struct StartInfo *start_info=(struct StartInfo *)data;
136 guint32 (*start_func)(void *);
141 g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper");
144 /* FIXME: GC problem here with recorded object
147 * This is recorded so CurrentThread can return the
150 TlsSetValue (current_object_key, start_info->obj);
151 start_func = start_info->func;
152 mono_domain_set (start_info->domain);
153 this = start_info->this;
156 tid=GetCurrentThreadId ();
159 mono_profiler_thread_start (tid);
161 if (mono_thread_start_cb)
162 mono_thread_start_cb (&tid);
167 g_message(G_GNUC_PRETTY_FUNCTION ": Start wrapper terminating");
170 mono_profiler_thread_end (tid);
177 mono_thread_create (MonoDomain *domain, gpointer func)
179 MonoClassField *field;
181 HANDLE thread_handle;
182 struct StartInfo *start_info;
185 thread = mono_object_new (domain, mono_defaults.thread_class);
187 field=mono_class_get_field_from_name(mono_defaults.thread_class, "system_thread_handle");
190 start_info=g_new0 (struct StartInfo, 1);
191 start_info->func = func;
192 start_info->obj = thread;
193 start_info->domain = domain;
194 /* start_info->this needs to be set? */
196 thread_handle = CreateThread(NULL, 0, start_wrapper, start_info, 0, &tid);
198 g_message(G_GNUC_PRETTY_FUNCTION ": Started thread ID %d (handle %p)",
201 g_assert (thread_handle);
205 *(gpointer *)(((char *)thread) + field->offset) = thread_handle;
210 HANDLE ves_icall_System_Threading_Thread_Thread_internal(MonoObject *this,
213 MonoMulticastDelegate *delegate = (MonoMulticastDelegate*)start;
214 guint32 (*start_func)(void *);
215 struct StartInfo *start_info;
220 g_message(G_GNUC_PRETTY_FUNCTION
221 ": Trying to start a new thread: this (%p) start (%p)",
225 start_func = delegate->delegate.method_ptr;
227 if(start_func==NULL) {
228 g_warning(G_GNUC_PRETTY_FUNCTION
229 ": Can't locate start method!");
232 /* This is freed in start_wrapper */
233 start_info = g_new0 (struct StartInfo, 1);
234 start_info->func = start_func;
235 start_info->this = delegate->delegate.target;
236 start_info->obj = this;
237 start_info->domain = mono_domain_get ();
239 thread=CreateThread(NULL, 0, start_wrapper, start_info,
240 CREATE_SUSPENDED, &tid);
242 g_warning(G_GNUC_PRETTY_FUNCTION
243 ": CreateThread error 0x%x", GetLastError());
250 g_message(G_GNUC_PRETTY_FUNCTION
251 ": Started thread ID %d (handle %p)", tid, thread);
258 void ves_icall_System_Threading_Thread_Thread_free_internal (MonoObject *this,
262 g_message (G_GNUC_PRETTY_FUNCTION ": Closing thread %p, handle %p",
266 CloseHandle (thread);
269 void ves_icall_System_Threading_Thread_Start_internal(MonoObject *this,
273 g_message(G_GNUC_PRETTY_FUNCTION ": Launching thread %p", this);
276 ResumeThread(thread);
279 void ves_icall_System_Threading_Thread_Sleep_internal(gint32 ms)
282 g_message(G_GNUC_PRETTY_FUNCTION ": Sleeping for %d ms", ms);
288 MonoAppDomain *ves_icall_System_Threading_Thread_CurrentThreadDomain_internal(void)
290 /* return the current app */
291 return mono_domain_get()->domain;
294 MonoObject *ves_icall_System_Threading_Thread_CurrentThread_internal(void)
298 /* Find the current thread object */
299 thread=TlsGetValue(current_object_key);
302 g_message (G_GNUC_PRETTY_FUNCTION ": returning %p", thread);
308 gboolean ves_icall_System_Threading_Thread_Join_internal(MonoObject *this,
309 int ms, HANDLE thread)
317 g_message (G_GNUC_PRETTY_FUNCTION ": joining thread handle %p, %d ms",
321 ret=WaitForSingleObject(thread, ms);
322 if(ret==WAIT_OBJECT_0) {
324 g_message (G_GNUC_PRETTY_FUNCTION ": join successful");
331 g_message (G_GNUC_PRETTY_FUNCTION ": join failed");
337 void ves_icall_System_Threading_Thread_SlotHash_store(MonoObject *data)
340 g_message(G_GNUC_PRETTY_FUNCTION ": Storing key %p", data);
343 /* Object location stored here */
344 TlsSetValue(slothash_key, data);
347 MonoObject *ves_icall_System_Threading_Thread_SlotHash_lookup(void)
351 data=TlsGetValue(slothash_key);
354 g_message(G_GNUC_PRETTY_FUNCTION ": Retrieved key %p", data);
360 static void mon_finalize (void *o, void *unused)
362 MonoThreadsSync *mon=(MonoThreadsSync *)o;
364 #ifdef THREAD_LOCK_DEBUG
365 g_message (G_GNUC_PRETTY_FUNCTION ": Finalizing sync");
368 CloseHandle (mon->monitor);
369 CloseHandle (mon->sema);
370 CloseHandle (mon->waiters_done);
373 static MonoThreadsSync *mon_new(void)
375 MonoThreadsSync *new;
378 new=(MonoThreadsSync *)GC_debug_malloc (sizeof(MonoThreadsSync), "sync", 1);
379 GC_debug_register_finalizer (new, mon_finalize, NULL, NULL, NULL);
381 /* This should be freed when the object that owns it is
384 new=(MonoThreadsSync *)g_new0 (MonoThreadsSync, 1);
387 new->monitor=CreateMutex(NULL, FALSE, NULL);
388 if(new->monitor==NULL) {
389 /* Throw some sort of system exception? (ditto for the
390 * sem and event handles below)
394 new->waiters_count=0;
395 new->was_broadcast=FALSE;
396 InitializeCriticalSection(&new->waiters_count_lock);
397 new->sema=CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
398 new->waiters_done=CreateEvent(NULL, FALSE, FALSE, NULL);
400 #ifdef THREAD_LOCK_DEBUG
401 g_message(G_GNUC_PRETTY_FUNCTION
402 ": ThreadsSync %p mutex created: %p, sem: %p, event: %p",
403 new, new->monitor, new->sema, new->waiters_done);
409 gboolean ves_icall_System_Threading_Monitor_Monitor_try_enter(MonoObject *obj,
412 MonoThreadsSync *mon;
415 #ifdef THREAD_LOCK_DEBUG
416 g_message(G_GNUC_PRETTY_FUNCTION
417 ": Trying to lock object %p in thread %d", obj,
418 GetCurrentThreadId());
421 EnterCriticalSection(&monitor_mutex);
423 mon=obj->synchronisation;
426 obj->synchronisation=mon;
429 /* Don't hold the monitor lock while waiting to acquire the
432 LeaveCriticalSection(&monitor_mutex);
434 /* Acquire the mutex */
435 #ifdef THREAD_LOCK_DEBUG
436 g_message(G_GNUC_PRETTY_FUNCTION ": Acquiring monitor mutex %p",
439 ret=WaitForSingleObject(mon->monitor, ms);
440 if(ret==WAIT_OBJECT_0) {
442 mon->tid=GetCurrentThreadId();
444 #ifdef THREAD_LOCK_DEBUG
445 g_message(G_GNUC_PRETTY_FUNCTION
446 ": object %p now locked %d times", obj, mon->count);
455 void ves_icall_System_Threading_Monitor_Monitor_exit(MonoObject *obj)
457 MonoThreadsSync *mon;
459 #ifdef THREAD_LOCK_DEBUG
460 g_message(G_GNUC_PRETTY_FUNCTION ": Unlocking %p in thread %d", obj,
461 GetCurrentThreadId());
464 /* No need to lock monitor_mutex here because we only adjust
465 * the monitor state if this thread already owns the lock
467 mon=obj->synchronisation;
473 if(mon->tid!=GetCurrentThreadId()) {
479 #ifdef THREAD_LOCK_DEBUG
480 g_message(G_GNUC_PRETTY_FUNCTION ": %p now locked %d times", obj,
485 mon->tid=0; /* FIXME: check that 0 isnt a valid id */
488 #ifdef THREAD_LOCK_DEBUG
489 g_message(G_GNUC_PRETTY_FUNCTION ": Releasing mutex %p", mon->monitor);
492 ReleaseMutex(mon->monitor);
495 gboolean ves_icall_System_Threading_Monitor_Monitor_test_owner(MonoObject *obj)
497 MonoThreadsSync *mon;
500 #ifdef THREAD_LOCK_DEBUG
501 g_message(G_GNUC_PRETTY_FUNCTION
502 ": Testing if %p is owned by thread %d", obj,
503 GetCurrentThreadId());
506 EnterCriticalSection(&monitor_mutex);
508 mon=obj->synchronisation;
513 if(mon->tid!=GetCurrentThreadId()) {
514 #ifdef THREAD_LOCK_DEBUG
515 g_message (G_GNUC_PRETTY_FUNCTION
516 ": object %p is owned by thread %d", obj, mon->tid);
525 LeaveCriticalSection(&monitor_mutex);
530 gboolean ves_icall_System_Threading_Monitor_Monitor_test_synchronised(MonoObject *obj)
532 MonoThreadsSync *mon;
535 #ifdef THREAD_LOCK_DEBUG
536 g_message(G_GNUC_PRETTY_FUNCTION
537 ": Testing if %p is owned by any thread", obj);
540 EnterCriticalSection(&monitor_mutex);
542 mon=obj->synchronisation;
551 g_assert(mon->count);
556 LeaveCriticalSection(&monitor_mutex);
562 void ves_icall_System_Threading_Monitor_Monitor_pulse(MonoObject *obj)
564 gboolean have_waiters;
565 MonoThreadsSync *mon;
567 #ifdef THREAD_LOCK_DEBUG
568 g_message("Pulsing %p in thread %d", obj, GetCurrentThreadId());
571 EnterCriticalSection(&monitor_mutex);
573 mon=obj->synchronisation;
575 LeaveCriticalSection(&monitor_mutex);
579 if(mon->tid!=GetCurrentThreadId()) {
580 LeaveCriticalSection(&monitor_mutex);
583 LeaveCriticalSection(&monitor_mutex);
585 EnterCriticalSection(&mon->waiters_count_lock);
586 have_waiters=(mon->waiters_count>0);
587 LeaveCriticalSection(&mon->waiters_count_lock);
589 if(have_waiters==TRUE) {
590 ReleaseSemaphore(mon->sema, 1, 0);
594 void ves_icall_System_Threading_Monitor_Monitor_pulse_all(MonoObject *obj)
596 gboolean have_waiters=FALSE;
597 MonoThreadsSync *mon;
599 #ifdef THREAD_LOCK_DEBUG
600 g_message("Pulsing all %p", obj);
603 EnterCriticalSection(&monitor_mutex);
605 mon=obj->synchronisation;
607 LeaveCriticalSection(&monitor_mutex);
611 if(mon->tid!=GetCurrentThreadId()) {
612 LeaveCriticalSection(&monitor_mutex);
615 LeaveCriticalSection(&monitor_mutex);
617 EnterCriticalSection(&mon->waiters_count_lock);
618 if(mon->waiters_count>0) {
619 mon->was_broadcast=TRUE;
623 if(have_waiters==TRUE) {
624 ReleaseSemaphore(mon->sema, mon->waiters_count, 0);
626 LeaveCriticalSection(&mon->waiters_count_lock);
628 WaitForSingleObject(mon->waiters_done, INFINITE);
629 mon->was_broadcast=FALSE;
631 LeaveCriticalSection(&mon->waiters_count_lock);
635 gboolean ves_icall_System_Threading_Monitor_Monitor_wait(MonoObject *obj,
638 gboolean last_waiter;
639 MonoThreadsSync *mon;
642 #ifdef THREAD_LOCK_DEBUG
643 g_message("Trying to wait for %p in thread %d with timeout %dms", obj,
644 GetCurrentThreadId(), ms);
647 EnterCriticalSection(&monitor_mutex);
649 mon=obj->synchronisation;
651 LeaveCriticalSection(&monitor_mutex);
655 if(mon->tid!=GetCurrentThreadId()) {
656 LeaveCriticalSection(&monitor_mutex);
659 LeaveCriticalSection(&monitor_mutex);
661 EnterCriticalSection(&mon->waiters_count_lock);
662 mon->waiters_count++;
663 LeaveCriticalSection(&mon->waiters_count_lock);
665 #ifdef THREAD_LOCK_DEBUG
666 g_message(G_GNUC_PRETTY_FUNCTION ": %p locked %d times", obj,
670 /* We need to put the lock count back afterwards */
671 save_count=mon->count;
673 while(mon->count>1) {
674 #ifdef THREAD_LOCK_DEBUG
675 g_message(G_GNUC_PRETTY_FUNCTION ": Releasing mutex %p",
679 ReleaseMutex(mon->monitor);
683 /* We're releasing this mutex */
686 #ifdef THREAD_LOCK_DEBUG
687 g_message(G_GNUC_PRETTY_FUNCTION ": Signalling monitor mutex %p",
691 SignalObjectAndWait(mon->monitor, mon->sema, INFINITE, FALSE);
693 EnterCriticalSection(&mon->waiters_count_lock);
694 mon->waiters_count++;
695 last_waiter=mon->was_broadcast && mon->waiters_count==0;
696 LeaveCriticalSection(&mon->waiters_count_lock);
699 #ifdef THREAD_LOCK_DEBUG
700 g_message(G_GNUC_PRETTY_FUNCTION ": Waiting for monitor mutex %p",
703 SignalObjectAndWait(mon->waiters_done, mon->monitor, INFINITE, FALSE);
705 #ifdef THREAD_LOCK_DEBUG
706 g_message(G_GNUC_PRETTY_FUNCTION ": Waiting for monitor mutex %p",
709 WaitForSingleObject(mon->monitor, INFINITE);
712 /* We've reclaimed this mutex */
713 mon->count=save_count;
714 mon->tid=GetCurrentThreadId();
716 /* Lock the mutex the required number of times */
717 while(save_count>1) {
718 #ifdef THREAD_LOCK_DEBUG
719 g_message(G_GNUC_PRETTY_FUNCTION
720 ": Waiting for monitor mutex %p", mon->monitor);
722 WaitForSingleObject(mon->monitor, INFINITE);
726 #ifdef THREAD_LOCK_DEBUG
727 g_message(G_GNUC_PRETTY_FUNCTION ": %p still locked %d times", obj,
734 /* FIXME: exitContext isnt documented */
735 gboolean ves_icall_System_Threading_WaitHandle_WaitAll_internal(MonoArray *mono_handles, gint32 ms, gboolean exitContext)
742 numhandles=mono_array_length(mono_handles);
743 handles=g_new0(HANDLE, numhandles);
744 for(i=0; i<numhandles; i++) {
745 handles[i]=mono_array_get(mono_handles, HANDLE, i);
752 ret=WaitForMultipleObjects(numhandles, handles, TRUE, ms);
756 if(ret==WAIT_FAILED) {
757 #ifdef THREAD_WAIT_DEBUG
758 g_message(G_GNUC_PRETTY_FUNCTION ": Wait failed");
761 } else if(ret==WAIT_TIMEOUT) {
762 #ifdef THREAD_WAIT_DEBUG
763 g_message(G_GNUC_PRETTY_FUNCTION ": Wait timed out");
771 /* FIXME: exitContext isnt documented */
772 gint32 ves_icall_System_Threading_WaitHandle_WaitAny_internal(MonoArray *mono_handles, gint32 ms, gboolean exitContext)
779 numhandles=mono_array_length(mono_handles);
780 handles=g_new0(HANDLE, numhandles);
781 for(i=0; i<numhandles; i++) {
782 handles[i]=mono_array_get(mono_handles, HANDLE, i);
789 ret=WaitForMultipleObjects(numhandles, handles, FALSE, ms);
793 #ifdef THREAD_WAIT_DEBUG
794 g_message(G_GNUC_PRETTY_FUNCTION ": returning %d", ret);
800 /* FIXME: exitContext isnt documented */
801 gboolean ves_icall_System_Threading_WaitHandle_WaitOne_internal(MonoObject *this, HANDLE handle, gint32 ms, gboolean exitContext)
805 #ifdef THREAD_WAIT_DEBUG
806 g_message(G_GNUC_PRETTY_FUNCTION ": waiting for %p", handle);
813 ret=WaitForSingleObject(handle, ms);
814 if(ret==WAIT_FAILED) {
815 #ifdef THREAD_WAIT_DEBUG
816 g_message(G_GNUC_PRETTY_FUNCTION ": Wait failed");
819 } else if(ret==WAIT_TIMEOUT) {
820 #ifdef THREAD_WAIT_DEBUG
821 g_message(G_GNUC_PRETTY_FUNCTION ": Wait timed out");
829 HANDLE ves_icall_System_Threading_Mutex_CreateMutex_internal (MonoBoolean owned,char *name) {
830 return(CreateMutex(NULL,owned,name));
833 void ves_icall_System_Threading_Mutex_ReleaseMutex_internal (HANDLE handle ) {
834 ReleaseMutex(handle);
837 HANDLE ves_icall_System_Threading_Events_CreateEvent_internal (MonoBoolean manual,
840 return (CreateEvent(NULL,manual,initial,name));
843 gboolean ves_icall_System_Threading_Events_SetEvent_internal (HANDLE handle) {
844 return (SetEvent(handle));
847 gboolean ves_icall_System_Threading_Events_ResetEvent_internal (HANDLE handle) {
848 return (ResetEvent(handle));
851 gint32 ves_icall_System_Threading_Interlocked_Increment_Int (gint32 *location)
853 return InterlockedIncrement (location);
856 gint64 ves_icall_System_Threading_Interlocked_Increment_Long (gint64 *location)
861 EnterCriticalSection(&interlocked_mutex);
863 lowret = InterlockedIncrement((gint32 *) location);
865 highret = InterlockedIncrement((gint32 *) location + 1);
867 highret = *((gint32 *) location + 1);
869 LeaveCriticalSection(&interlocked_mutex);
871 return (gint64) highret << 32 | (gint64) lowret;
874 gint32 ves_icall_System_Threading_Interlocked_Decrement_Int (gint32 *location)
876 return InterlockedDecrement(location);
879 gint64 ves_icall_System_Threading_Interlocked_Decrement_Long (gint64 * location)
884 EnterCriticalSection(&interlocked_mutex);
886 lowret = InterlockedDecrement((gint32 *) location);
888 highret = InterlockedDecrement((gint32 *) location + 1);
890 highret = *((gint32 *) location + 1);
892 LeaveCriticalSection(&interlocked_mutex);
894 return (gint64) highret << 32 | (gint64) lowret;
897 gint32 ves_icall_System_Threading_Interlocked_Exchange_Int (gint32 *location1, gint32 value)
899 return InterlockedExchange(location1, value);
902 MonoObject * ves_icall_System_Threading_Interlocked_Exchange_Object (MonoObject **location1, MonoObject *value)
904 return (MonoObject *) InterlockedExchangePointer((gpointer *) location1, value);
907 gfloat ves_icall_System_Threading_Interlocked_Exchange_Single (gfloat *location1, gfloat value)
909 IntFloatUnion val, ret;
912 ret.ival = InterlockedExchange((gint32 *) location1, val.ival);
917 gint32 ves_icall_System_Threading_Interlocked_CompareExchange_Int(gint32 *location1, gint32 value, gint32 comparand)
919 return InterlockedCompareExchange(location1, value, comparand);
922 MonoObject * ves_icall_System_Threading_Interlocked_CompareExchange_Object (MonoObject **location1, MonoObject *value, MonoObject *comparand)
924 return (MonoObject *) InterlockedCompareExchangePointer((gpointer *) location1, value, comparand);
927 gfloat ves_icall_System_Threading_Interlocked_CompareExchange_Single (gfloat *location1, gfloat value, gfloat comparand)
929 IntFloatUnion val, ret, cmp;
932 cmp.fval = comparand;
933 ret.ival = InterlockedCompareExchange((gint32 *) location1, val.ival, cmp.ival);
938 void mono_thread_init(MonoDomain *domain, MonoThreadStartCB start_cb)
940 MonoClass *thread_class;
942 /* Build a System.Threading.Thread object instance to return
943 * for the main line's Thread.CurrentThread property.
945 thread_class=mono_class_from_name(mono_defaults.corlib, "System.Threading", "Thread");
947 /* I wonder what happens if someone tries to destroy this
948 * object? In theory, I guess the whole program should act as
949 * though exit() were called :-)
952 g_message(G_GNUC_PRETTY_FUNCTION
953 ": Starting to build main Thread object");
955 main_thread = mono_object_new (domain, thread_class);
957 g_message(G_GNUC_PRETTY_FUNCTION
958 ": Finished building main Thread object: %p", main_thread);
961 InitializeCriticalSection(&threads_mutex);
962 InitializeCriticalSection(&monitor_mutex);
963 InitializeCriticalSection(&interlocked_mutex);
965 current_object_key=TlsAlloc();
967 g_message (G_GNUC_PRETTY_FUNCTION ": Allocated current_object_key %d",
971 TlsSetValue(current_object_key, main_thread);
973 mono_thread_start_cb = start_cb;
975 slothash_key=TlsAlloc();
978 void mono_thread_cleanup(void)
980 HANDLE wait[MAXIMUM_WAIT_OBJECTS];
983 /* join each thread that's still running */
985 g_message("Joining each running thread...");
990 g_message("No threads");
996 EnterCriticalSection (&threads_mutex);
998 g_message("There are %d threads to join", threads->len);
999 for(i=0; i<threads->len; i++) {
1000 g_message("Waiting for: %d",
1001 g_array_index(threads, guint32, i));
1005 for(i=0; i<MAXIMUM_WAIT_OBJECTS && i<threads->len; i++) {
1006 wait[i]=OpenThread (THREAD_ALL_ACCESS, FALSE,
1007 g_array_index(threads, guint32, i));
1011 g_message("%d threads to wait for in this batch", i);
1014 LeaveCriticalSection (&threads_mutex);
1016 WaitForMultipleObjects(i, wait, TRUE, INFINITE);
1018 for(j=0; j<i; j++) {
1019 CloseHandle (wait[j]);
1023 g_array_free(threads, FALSE);