]> git.sesse.net Git - vlc/blob - src/misc/threads.c
vlc_threadobj(): returns the object nesting the current thread
[vlc] / src / misc / threads.c
1 /*****************************************************************************
2  * threads.c : threads implementation for the VideoLAN client
3  *****************************************************************************
4  * Copyright (C) 1999-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jean-Marc Dressler <polux@via.ecp.fr>
8  *          Samuel Hocevar <sam@zoy.org>
9  *          Gildas Bazin <gbazin@netcourrier.com>
10  *          Clément Sténac
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc/vlc.h>
32
33 #include "libvlc.h"
34 #include <assert.h>
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h>
37 #endif
38
39 #define VLC_THREADS_UNINITIALIZED  0
40 #define VLC_THREADS_PENDING        1
41 #define VLC_THREADS_ERROR          2
42 #define VLC_THREADS_READY          3
43
44 /*****************************************************************************
45  * Global mutex for lazy initialization of the threads system
46  *****************************************************************************/
47 static volatile unsigned i_initializations = 0;
48
49 #if defined( LIBVLC_USE_PTHREAD )
50 static pthread_mutex_t once_mutex = PTHREAD_MUTEX_INITIALIZER;
51 #endif
52
53 /**
54  * Global process-wide VLC object.
55  * Contains inter-instance data, such as the module cache and global mutexes.
56  */
57 static libvlc_global_data_t *p_root;
58
59 libvlc_global_data_t *vlc_global( void )
60 {
61     assert( i_initializations > 0 );
62     return p_root;
63 }
64
65 /**
66  * Object running the current thread
67  */
68 static vlc_threadvar_t thread_object_key;
69
70 vlc_object_t *vlc_threadobj (void)
71 {
72     return vlc_threadvar_get (&thread_object_key);
73 }
74
75 vlc_threadvar_t msg_context_global_key;
76
77 #if defined(LIBVLC_USE_PTHREAD)
78 static inline unsigned long vlc_threadid (void)
79 {
80      union { pthread_t th; unsigned long int i; } v = { };
81      v.th = pthread_self ();
82      return v.i;
83 }
84
85 /*****************************************************************************
86  * vlc_thread_fatal: Report an error from the threading layer
87  *****************************************************************************
88  * This is mostly meant for debugging.
89  *****************************************************************************/
90 void vlc_pthread_fatal (const char *action, int error,
91                         const char *file, unsigned line)
92 {
93     fprintf (stderr, "LibVLC fatal error %s in thread %lu at %s:%u: %d\n",
94              action, vlc_threadid (), file, line, error);
95     fflush (stderr);
96
97     /* Sometimes strerror_r() crashes too, so make sure we print an error
98      * message before we invoke it */
99 #ifdef __GLIBC__
100     /* Avoid the strerror_r() prototype brain damage in glibc */
101     errno = error;
102     fprintf (stderr, " Error message: %m\n");
103 #else
104     char buf[1000];
105     const char *msg;
106
107     switch (strerror_r (error, buf, sizeof (buf)))
108     {
109         case 0:
110             msg = buf;
111             break;
112         case ERANGE: /* should never happen */
113             msg = "unknwon (too big to display)";
114             break;
115         default:
116             msg = "unknown (invalid error number)";
117             break;
118     }
119     fprintf (stderr, " Error message: %s\n", msg);
120 #endif
121
122     fflush (stderr);
123     abort ();
124 }
125 #else
126 void vlc_pthread_fatal (const char *action, int error,
127                         const char *file, unsigned line)
128 {
129     (void)action; (void)error; (void)file; (void)line;
130     abort();
131 }
132 #endif
133
134 /*****************************************************************************
135  * vlc_threads_init: initialize threads system
136  *****************************************************************************
137  * This function requires lazy initialization of a global lock in order to
138  * keep the library really thread-safe. Some architectures don't support this
139  * and thus do not guarantee the complete reentrancy.
140  *****************************************************************************/
141 int vlc_threads_init( void )
142 {
143     int i_ret = VLC_SUCCESS;
144
145     /* If we have lazy mutex initialization, use it. Otherwise, we just
146      * hope nothing wrong happens. */
147 #if defined( LIBVLC_USE_PTHREAD )
148     pthread_mutex_lock( &once_mutex );
149 #endif
150
151     if( i_initializations == 0 )
152     {
153         p_root = vlc_custom_create( NULL, sizeof( *p_root ),
154                                     VLC_OBJECT_GENERIC, "root" );
155         if( p_root == NULL )
156         {
157             i_ret = VLC_ENOMEM;
158             goto out;
159         }
160
161         /* We should be safe now. Do all the initialization stuff we want. */
162         vlc_threadvar_create( &thread_object_key, NULL );
163         vlc_threadvar_create( &msg_context_global_key, msg_StackDestroy );
164     }
165     i_initializations++;
166
167 out:
168     /* If we have lazy mutex initialization support, unlock the mutex.
169      * Otherwize, we are screwed. */
170 #if defined( LIBVLC_USE_PTHREAD )
171     pthread_mutex_unlock( &once_mutex );
172 #endif
173
174     return i_ret;
175 }
176
177 /*****************************************************************************
178  * vlc_threads_end: stop threads system
179  *****************************************************************************
180  * FIXME: This function is far from being threadsafe.
181  *****************************************************************************/
182 void vlc_threads_end( void )
183 {
184 #if defined( LIBVLC_USE_PTHREAD )
185     pthread_mutex_lock( &once_mutex );
186 #endif
187
188     assert( i_initializations > 0 );
189
190     if( i_initializations == 1 )
191     {
192         vlc_object_release( p_root );
193         vlc_threadvar_delete( &msg_context_global_key );
194         vlc_threadvar_delete( &thread_object_key );
195     }
196     i_initializations--;
197
198 #if defined( LIBVLC_USE_PTHREAD )
199     pthread_mutex_unlock( &once_mutex );
200 #endif
201 }
202
203 #if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6)
204 /* This is not prototyped under glibc, though it exists. */
205 int pthread_mutexattr_setkind_np( pthread_mutexattr_t *attr, int kind );
206 #endif
207
208 /*****************************************************************************
209  * vlc_mutex_init: initialize a mutex
210  *****************************************************************************/
211 int vlc_mutex_init( vlc_mutex_t *p_mutex )
212 {
213 #if defined( LIBVLC_USE_PTHREAD )
214     pthread_mutexattr_t attr;
215     int                 i_result;
216
217     pthread_mutexattr_init( &attr );
218
219 # ifndef NDEBUG
220     /* Create error-checking mutex to detect problems more easily. */
221 #  if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6)
222     pthread_mutexattr_setkind_np( &attr, PTHREAD_MUTEX_ERRORCHECK_NP );
223 #  else
224     pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_ERRORCHECK );
225 #  endif
226 # endif
227     i_result = pthread_mutex_init( p_mutex, &attr );
228     pthread_mutexattr_destroy( &attr );
229     return i_result;
230 #elif defined( UNDER_CE )
231     InitializeCriticalSection( &p_mutex->csection );
232     return 0;
233
234 #elif defined( WIN32 )
235     *p_mutex = CreateMutex( 0, FALSE, 0 );
236     return (*p_mutex != NULL) ? 0 : ENOMEM;
237
238 #elif defined( HAVE_KERNEL_SCHEDULER_H )
239     /* check the arguments and whether it's already been initialized */
240     if( p_mutex == NULL )
241     {
242         return B_BAD_VALUE;
243     }
244
245     if( p_mutex->init == 9999 )
246     {
247         return EALREADY;
248     }
249
250     p_mutex->lock = create_sem( 1, "BeMutex" );
251     if( p_mutex->lock < B_NO_ERROR )
252     {
253         return( -1 );
254     }
255
256     p_mutex->init = 9999;
257     return B_OK;
258
259 #endif
260 }
261
262 /*****************************************************************************
263  * vlc_mutex_init: initialize a recursive mutex (Do not use)
264  *****************************************************************************/
265 int vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
266 {
267 #if defined( LIBVLC_USE_PTHREAD )
268     pthread_mutexattr_t attr;
269     int                 i_result;
270
271     pthread_mutexattr_init( &attr );
272 #  if defined (__GLIBC__) && (__GLIBC_MINOR__ < 6)
273     pthread_mutexattr_setkind_np( &attr, PTHREAD_MUTEX_RECURSIVE_NP );
274 #  else
275     pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE );
276 #  endif
277     i_result = pthread_mutex_init( p_mutex, &attr );
278     pthread_mutexattr_destroy( &attr );
279     return( i_result );
280 #elif defined( WIN32 )
281     /* Create mutex returns a recursive mutex */
282     *p_mutex = CreateMutex( 0, FALSE, 0 );
283     return (*p_mutex != NULL) ? 0 : ENOMEM;
284 #else
285 # error Unimplemented!
286 #endif
287 }
288
289
290 /*****************************************************************************
291  * vlc_mutex_destroy: destroy a mutex, inner version
292  *****************************************************************************/
293 void __vlc_mutex_destroy( const char * psz_file, int i_line, vlc_mutex_t *p_mutex )
294 {
295 #if defined( LIBVLC_USE_PTHREAD )
296     int val = pthread_mutex_destroy( p_mutex );
297     VLC_THREAD_ASSERT ("destroying mutex");
298
299 #elif defined( UNDER_CE )
300     VLC_UNUSED( psz_file); VLC_UNUSED( i_line );
301
302     DeleteCriticalSection( &p_mutex->csection );
303
304 #elif defined( WIN32 )
305     VLC_UNUSED( psz_file); VLC_UNUSED( i_line );
306
307     CloseHandle( *p_mutex );
308
309 #elif defined( HAVE_KERNEL_SCHEDULER_H )
310     if( p_mutex->init == 9999 )
311         delete_sem( p_mutex->lock );
312
313     p_mutex->init = 0;
314
315 #endif
316 }
317
318 /*****************************************************************************
319  * vlc_cond_init: initialize a condition
320  *****************************************************************************/
321 int __vlc_cond_init( vlc_cond_t *p_condvar )
322 {
323 #if defined( LIBVLC_USE_PTHREAD )
324     pthread_condattr_t attr;
325     int ret;
326
327     ret = pthread_condattr_init (&attr);
328     if (ret)
329         return ret;
330
331 # if !defined (_POSIX_CLOCK_SELECTION)
332    /* Fairly outdated POSIX support (that was defined in 2001) */
333 #  define _POSIX_CLOCK_SELECTION (-1)
334 # endif
335 # if (_POSIX_CLOCK_SELECTION >= 0)
336     /* NOTE: This must be the same clock as the one in mtime.c */
337     pthread_condattr_setclock (&attr, CLOCK_MONOTONIC);
338 # endif
339
340     ret = pthread_cond_init (p_condvar, &attr);
341     pthread_condattr_destroy (&attr);
342     return ret;
343
344 #elif defined( UNDER_CE ) || defined( WIN32 )
345     /* Initialize counter */
346     p_condvar->i_waiting_threads = 0;
347
348     /* Create an auto-reset event. */
349     p_condvar->event = CreateEvent( NULL,   /* no security */
350                                     FALSE,  /* auto-reset event */
351                                     FALSE,  /* start non-signaled */
352                                     NULL ); /* unnamed */
353     return !p_condvar->event;
354
355 #elif defined( HAVE_KERNEL_SCHEDULER_H )
356     if( !p_condvar )
357     {
358         return B_BAD_VALUE;
359     }
360
361     if( p_condvar->init == 9999 )
362     {
363         return EALREADY;
364     }
365
366     p_condvar->thread = -1;
367     p_condvar->init = 9999;
368     return 0;
369
370 #endif
371 }
372
373 /*****************************************************************************
374  * vlc_cond_destroy: destroy a condition, inner version
375  *****************************************************************************/
376 void __vlc_cond_destroy( const char * psz_file, int i_line, vlc_cond_t *p_condvar )
377 {
378 #if defined( LIBVLC_USE_PTHREAD )
379     int val = pthread_cond_destroy( p_condvar );
380     VLC_THREAD_ASSERT ("destroying condition");
381
382 #elif defined( UNDER_CE ) || defined( WIN32 )
383     VLC_UNUSED( psz_file); VLC_UNUSED( i_line );
384
385     CloseHandle( p_condvar->event );
386
387 #elif defined( HAVE_KERNEL_SCHEDULER_H )
388     p_condvar->init = 0;
389
390 #endif
391 }
392
393 /*****************************************************************************
394  * vlc_tls_create: create a thread-local variable
395  *****************************************************************************/
396 int vlc_threadvar_create( vlc_threadvar_t *p_tls, void (*destr) (void *) )
397 {
398     int i_ret;
399
400 #if defined( LIBVLC_USE_PTHREAD )
401     i_ret =  pthread_key_create( p_tls, destr );
402 #elif defined( UNDER_CE )
403     i_ret = ENOSYS;
404 #elif defined( WIN32 )
405     *p_tls = TlsAlloc();
406     i_ret = (*p_tls == TLS_OUT_OF_INDEXES) ? EAGAIN : 0;
407 #else
408 # error Unimplemented!
409 #endif
410     return i_ret;
411 }
412
413 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
414 {
415 #if defined( LIBVLC_USE_PTHREAD )
416     pthread_key_delete (*p_tls);
417 #elif defined( UNDER_CE )
418 #elif defined( WIN32 )
419     TlsFree (*p_tls);
420 #else
421 # error Unimplemented!
422 #endif
423 }
424
425 struct vlc_thread_boot
426 {
427     void * (*entry) (void *);
428     vlc_object_t *object;
429 };
430
431 #if defined (LIBVLC_USE_PTHREAD)
432 # define THREAD_RTYPE void *
433 # define THREAD_RVAL  NULL
434 #elif defined (WIN32)
435 # define THREAD_RTYPE __stdcall unsigned
436 # define THREAD_RVAL 0
437 #endif
438
439 static THREAD_RTYPE thread_entry (void *data)
440 {
441     vlc_object_t *obj = ((struct vlc_thread_boot *)data)->object;
442     void *(*func) (void *) = ((struct vlc_thread_boot *)data)->entry;
443
444     free (data);
445     vlc_threadvar_set (&thread_object_key, obj);
446     msg_Dbg (obj, "thread started");
447     func (obj);
448     msg_Dbg (obj, "thread ended");
449     return THREAD_RVAL;
450 }
451
452 /*****************************************************************************
453  * vlc_thread_create: create a thread, inner version
454  *****************************************************************************
455  * Note that i_priority is only taken into account on platforms supporting
456  * userland real-time priority threads.
457  *****************************************************************************/
458 int __vlc_thread_create( vlc_object_t *p_this, const char * psz_file, int i_line,
459                          const char *psz_name, void * ( *func ) ( void * ),
460                          int i_priority, bool b_wait )
461 {
462     int i_ret;
463     vlc_object_internals_t *p_priv = vlc_internals( p_this );
464
465     struct vlc_thread_boot *boot = malloc (sizeof (*boot));
466     if (boot == NULL)
467         return errno;
468     boot->entry = func;
469     boot->object = p_this;
470
471     vlc_mutex_lock( &p_this->object_lock );
472
473 #if defined( LIBVLC_USE_PTHREAD )
474     i_ret = pthread_create( &p_priv->thread_id, NULL, thread_entry, boot );
475
476 #ifndef __APPLE__
477     if( config_GetInt( p_this, "rt-priority" ) > 0 )
478 #endif
479     {
480         int i_error, i_policy;
481         struct sched_param param;
482
483         memset( &param, 0, sizeof(struct sched_param) );
484         if( config_GetType( p_this, "rt-offset" ) )
485             i_priority += config_GetInt( p_this, "rt-offset" );
486         if( i_priority <= 0 )
487         {
488             param.sched_priority = (-1) * i_priority;
489             i_policy = SCHED_OTHER;
490         }
491         else
492         {
493             param.sched_priority = i_priority;
494             i_policy = SCHED_RR;
495         }
496         if( (i_error = pthread_setschedparam( p_priv->thread_id,
497                                                i_policy, &param )) )
498         {
499             errno = i_error;
500             msg_Warn( p_this, "couldn't set thread priority (%s:%d): %m",
501                       psz_file, i_line );
502             i_priority = 0;
503         }
504     }
505 #ifndef __APPLE__
506     else
507         i_priority = 0;
508 #endif
509
510 #elif defined( WIN32 ) || defined( UNDER_CE )
511     {
512         /* When using the MSVCRT C library you have to use the _beginthreadex
513          * function instead of CreateThread, otherwise you'll end up with
514          * memory leaks and the signal functions not working (see Microsoft
515          * Knowledge Base, article 104641) */
516 #if defined( UNDER_CE )
517         HANDLE hThread = CreateThread( NULL, 0, thread_entry,
518                                        (LPVOID)boot, CREATE_SUSPENDED,
519                                         NULL );
520 #else
521         HANDLE hThread = (HANDLE)(uintptr_t)
522             _beginthreadex( NULL, 0, thread_entry, boot,
523                             CREATE_SUSPENDED, NULL );
524 #endif
525         p_priv->thread_id = hThread;
526         ResumeThread(hThread);
527     }
528
529     i_ret = ( p_priv->thread_id ? 0 : errno );
530
531     if( !i_ret && i_priority )
532     {
533         if( !SetThreadPriority(p_priv->thread_id, i_priority) )
534         {
535             msg_Warn( p_this, "couldn't set a faster priority" );
536             i_priority = 0;
537         }
538     }
539
540 #elif defined( HAVE_KERNEL_SCHEDULER_H )
541     p_priv->thread_id = spawn_thread( (thread_func)thread_entry, psz_name,
542                                       i_priority, p_data );
543     i_ret = resume_thread( p_priv->thread_id );
544
545 #endif
546
547     if( i_ret == 0 )
548     {
549         if( b_wait )
550         {
551             msg_Dbg( p_this, "waiting for thread completion" );
552             vlc_object_wait( p_this );
553         }
554
555         p_priv->b_thread = true;
556         msg_Dbg( p_this, "thread %lu (%s) created at priority %d (%s:%d)",
557                  (unsigned long)p_priv->thread_id, psz_name, i_priority,
558                  psz_file, i_line );
559     }
560     else
561     {
562         errno = i_ret;
563         msg_Err( p_this, "%s thread could not be created at %s:%d (%m)",
564                          psz_name, psz_file, i_line );
565     }
566
567     vlc_mutex_unlock( &p_this->object_lock );
568     return i_ret;
569 }
570
571 /*****************************************************************************
572  * vlc_thread_set_priority: set the priority of the current thread when we
573  * couldn't set it in vlc_thread_create (for instance for the main thread)
574  *****************************************************************************/
575 int __vlc_thread_set_priority( vlc_object_t *p_this, const char * psz_file,
576                                int i_line, int i_priority )
577 {
578     vlc_object_internals_t *p_priv = vlc_internals( p_this );
579
580 #if defined( LIBVLC_USE_PTHREAD )
581 # ifndef __APPLE__
582     if( config_GetInt( p_this, "rt-priority" ) > 0 )
583 # endif
584     {
585         int i_error, i_policy;
586         struct sched_param param;
587
588         memset( &param, 0, sizeof(struct sched_param) );
589         if( config_GetType( p_this, "rt-offset" ) )
590             i_priority += config_GetInt( p_this, "rt-offset" );
591         if( i_priority <= 0 )
592         {
593             param.sched_priority = (-1) * i_priority;
594             i_policy = SCHED_OTHER;
595         }
596         else
597         {
598             param.sched_priority = i_priority;
599             i_policy = SCHED_RR;
600         }
601         if( !p_priv->thread_id )
602             p_priv->thread_id = pthread_self();
603         if( (i_error = pthread_setschedparam( p_priv->thread_id,
604                                                i_policy, &param )) )
605         {
606             errno = i_error;
607             msg_Warn( p_this, "couldn't set thread priority (%s:%d): %m",
608                       psz_file, i_line );
609             i_priority = 0;
610         }
611     }
612
613 #elif defined( WIN32 ) || defined( UNDER_CE )
614     VLC_UNUSED( psz_file); VLC_UNUSED( i_line );
615
616     if( !p_priv->thread_id )
617         p_priv->thread_id = GetCurrentThread();
618     if( !SetThreadPriority(p_priv->thread_id, i_priority) )
619     {
620         msg_Warn( p_this, "couldn't set a faster priority" );
621         return 1;
622     }
623
624 #endif
625
626     return 0;
627 }
628
629 /*****************************************************************************
630  * vlc_thread_ready: tell the parent thread we were successfully spawned
631  *****************************************************************************/
632 void __vlc_thread_ready( vlc_object_t *p_this )
633 {
634     vlc_object_signal( p_this );
635 }
636
637 /*****************************************************************************
638  * vlc_thread_join: wait until a thread exits, inner version
639  *****************************************************************************/
640 void __vlc_thread_join( vlc_object_t *p_this, const char * psz_file, int i_line )
641 {
642     vlc_object_internals_t *p_priv = vlc_internals( p_this );
643     int i_ret = 0;
644
645 #if defined( LIBVLC_USE_PTHREAD )
646     /* Make sure we do return if we are calling vlc_thread_join()
647      * from the joined thread */
648     if (pthread_equal (pthread_self (), p_priv->thread_id))
649         i_ret = pthread_detach (p_priv->thread_id);
650     else
651         i_ret = pthread_join (p_priv->thread_id, NULL);
652
653 #elif defined( UNDER_CE ) || defined( WIN32 )
654     HMODULE hmodule;
655     BOOL (WINAPI *OurGetThreadTimes)( HANDLE, FILETIME*, FILETIME*,
656                                       FILETIME*, FILETIME* );
657     FILETIME create_ft, exit_ft, kernel_ft, user_ft;
658     int64_t real_time, kernel_time, user_time;
659     HANDLE hThread;
660
661     /*
662     ** object will close its thread handle when destroyed, duplicate it here
663     ** to be on the safe side
664     */
665     if( ! DuplicateHandle(GetCurrentProcess(),
666             p_priv->thread_id,
667             GetCurrentProcess(),
668             &hThread,
669             0,
670             FALSE,
671             DUPLICATE_SAME_ACCESS) )
672     {
673         p_priv->b_thread = false;
674         i_ret = GetLastError();
675         goto error;
676     }
677
678     WaitForSingleObject( hThread, INFINITE );
679
680 #if defined( UNDER_CE )
681     hmodule = GetModuleHandle( _T("COREDLL") );
682 #else
683     hmodule = GetModuleHandle( _T("KERNEL32") );
684 #endif
685     OurGetThreadTimes = (BOOL (WINAPI*)( HANDLE, FILETIME*, FILETIME*,
686                                          FILETIME*, FILETIME* ))
687         GetProcAddress( hmodule, _T("GetThreadTimes") );
688
689     if( OurGetThreadTimes &&
690         OurGetThreadTimes( hThread,
691                            &create_ft, &exit_ft, &kernel_ft, &user_ft ) )
692     {
693         real_time =
694           ((((int64_t)exit_ft.dwHighDateTime)<<32)| exit_ft.dwLowDateTime) -
695           ((((int64_t)create_ft.dwHighDateTime)<<32)| create_ft.dwLowDateTime);
696         real_time /= 10;
697
698         kernel_time =
699           ((((int64_t)kernel_ft.dwHighDateTime)<<32)|
700            kernel_ft.dwLowDateTime) / 10;
701
702         user_time =
703           ((((int64_t)user_ft.dwHighDateTime)<<32)|
704            user_ft.dwLowDateTime) / 10;
705
706         msg_Dbg( p_this, "thread times: "
707                  "real %"PRId64"m%fs, kernel %"PRId64"m%fs, user %"PRId64"m%fs",
708                  real_time/60/1000000,
709                  (double)((real_time%(60*1000000))/1000000.0),
710                  kernel_time/60/1000000,
711                  (double)((kernel_time%(60*1000000))/1000000.0),
712                  user_time/60/1000000,
713                  (double)((user_time%(60*1000000))/1000000.0) );
714     }
715     CloseHandle( hThread );
716 error:
717
718 #elif defined( HAVE_KERNEL_SCHEDULER_H )
719     int32_t exit_value;
720     i_ret = (B_OK == wait_for_thread( p_priv->thread_id, &exit_value ));
721
722 #endif
723
724     if( i_ret )
725     {
726         errno = i_ret;
727         msg_Err( p_this, "thread_join(%lu) failed at %s:%d (%m)",
728                          (unsigned long)p_priv->thread_id, psz_file, i_line );
729     }
730     else
731         msg_Dbg( p_this, "thread %lu joined (%s:%d)",
732                          (unsigned long)p_priv->thread_id, psz_file, i_line );
733
734     p_priv->b_thread = false;
735 }