]> git.sesse.net Git - vlc/blob - src/os2/thread.c
vlccore: fix a linked list of threadvar on OS/2
[vlc] / src / os2 / thread.c
1 /*****************************************************************************
2  * thread.c : OS/2 back-end for LibVLC
3  *****************************************************************************
4  * Copyright (C) 1999-2011 VLC authors and VideoLAN
5  *
6  * Authors: KO Myung-Hun <komh@chollian.net>
7  *          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  *          Rémi Denis-Courmont
12  *          Pierre Ynard
13  *
14  * This program is free software; you can redistribute it and/or modify it
15  * under the terms of the GNU Lesser General Public License as published by
16  * the Free Software Foundation; either version 2.1 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU Lesser General Public License for more details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with this program; if not, write to the Free Software Foundation,
26  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34
35 #include "libvlc.h"
36 #include <stdarg.h>
37 #include <assert.h>
38 #include <limits.h>
39 #include <errno.h>
40 #include <time.h>
41
42 static vlc_threadvar_t thread_key;
43
44 /**
45  * Per-thread data
46  */
47 struct vlc_thread
48 {
49     TID            tid;
50     HEV            cancel_event;
51     HEV            done_event;
52
53     bool           detached;
54     bool           killable;
55     bool           killed;
56     vlc_cleanup_t *cleaners;
57
58     void        *(*entry) (void *);
59     void          *data;
60 };
61
62 static void vlc_cancel_self (PVOID dummy);
63
64 static ULONG vlc_DosWaitEventSemEx( HEV hev, ULONG ulTimeout, BOOL fCancelable )
65 {
66     HMUX      hmux;
67     SEMRECORD asr[ 2 ];
68     ULONG     ulUser;
69     int       n;
70     ULONG     rc;
71
72     struct vlc_thread *th = vlc_threadvar_get( thread_key );
73     if( th == NULL || !fCancelable )
74     {
75         /* Main thread - cannot be cancelled anyway
76          * Alien thread - out of our control
77          */
78         if( hev != NULLHANDLE )
79             return DosWaitEventSem( hev, ulTimeout );
80
81         return DosSleep( ulTimeout );
82     }
83
84     n = 0;
85     if( hev != NULLHANDLE )
86     {
87         asr[ n ].hsemCur = ( HSEM )hev;
88         asr[ n ].ulUser  = 0;
89         n++;
90     }
91     asr[ n ].hsemCur = ( HSEM )th->cancel_event;
92     asr[ n ].ulUser  = 0xFFFF;
93     n++;
94
95     DosCreateMuxWaitSem( NULL, &hmux, n, asr, DCMW_WAIT_ANY );
96     rc = DosWaitMuxWaitSem( hmux, ulTimeout, &ulUser );
97     DosCloseMuxWaitSem( hmux );
98     if( rc )
99         return rc;
100
101     if( ulUser == 0xFFFF )
102     {
103         vlc_cancel_self( th );
104         return ERROR_INTERRUPT;
105     }
106
107     return NO_ERROR;
108 }
109
110 static ULONG vlc_WaitForSingleObject (HEV hev, ULONG ulTimeout)
111 {
112     return vlc_DosWaitEventSemEx( hev, ulTimeout, TRUE );
113 }
114
115 static ULONG vlc_Sleep (ULONG ulTimeout)
116 {
117     ULONG rc = vlc_DosWaitEventSemEx( NULLHANDLE, ulTimeout, TRUE );
118
119     return ( rc != ERROR_TIMEOUT ) ? rc : 0;
120 }
121
122 static vlc_mutex_t super_mutex;
123 static vlc_cond_t  super_variable;
124 extern vlc_rwlock_t config_lock, msg_lock;
125
126 int _CRT_init(void);
127 void _CRT_term(void);
128
129 unsigned long _System _DLL_InitTerm(unsigned long, unsigned long);
130
131 unsigned long _System _DLL_InitTerm(unsigned long hmod, unsigned long flag)
132 {
133     VLC_UNUSED (hmod);
134
135     switch (flag)
136     {
137         case 0 :    /* Initialization */
138             if(_CRT_init() == -1)
139                 return 0;
140
141             vlc_mutex_init (&super_mutex);
142             vlc_cond_init (&super_variable);
143             vlc_threadvar_create (&thread_key, NULL);
144             vlc_rwlock_init (&config_lock);
145             vlc_rwlock_init (&msg_lock);
146             vlc_CPU_init ();
147
148             return 1;
149
150         case 1 :    /* Termination */
151             vlc_rwlock_destroy (&msg_lock);
152             vlc_rwlock_destroy (&config_lock);
153             vlc_threadvar_delete (&thread_key);
154             vlc_cond_destroy (&super_variable);
155             vlc_mutex_destroy (&super_mutex);
156
157             _CRT_term();
158
159             return 1;
160     }
161
162     return 0;   /* Failed */
163 }
164
165 /*** Mutexes ***/
166 void vlc_mutex_init( vlc_mutex_t *p_mutex )
167 {
168     /* This creates a recursive mutex. This is OK as fast mutexes have
169      * no defined behavior in case of recursive locking. */
170     DosCreateMutexSem( NULL, &p_mutex->hmtx, 0, FALSE );
171     p_mutex->dynamic = true;
172 }
173
174 void vlc_mutex_init_recursive( vlc_mutex_t *p_mutex )
175 {
176     DosCreateMutexSem( NULL, &p_mutex->hmtx, 0, FALSE );
177     p_mutex->dynamic = true;
178 }
179
180
181 void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
182 {
183     assert (p_mutex->dynamic);
184     DosCloseMutexSem( p_mutex->hmtx );
185 }
186
187 void vlc_mutex_lock (vlc_mutex_t *p_mutex)
188 {
189     if (!p_mutex->dynamic)
190     {   /* static mutexes */
191         int canc = vlc_savecancel ();
192         assert (p_mutex != &super_mutex); /* this one cannot be static */
193
194         vlc_mutex_lock (&super_mutex);
195         while (p_mutex->locked)
196         {
197             p_mutex->contention++;
198             vlc_cond_wait (&super_variable, &super_mutex);
199             p_mutex->contention--;
200         }
201         p_mutex->locked = true;
202         vlc_mutex_unlock (&super_mutex);
203         vlc_restorecancel (canc);
204         return;
205     }
206
207     DosRequestMutexSem(p_mutex->hmtx, SEM_INDEFINITE_WAIT);
208 }
209
210 int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
211 {
212     if (!p_mutex->dynamic)
213     {   /* static mutexes */
214         int ret = EBUSY;
215
216         assert (p_mutex != &super_mutex); /* this one cannot be static */
217         vlc_mutex_lock (&super_mutex);
218         if (!p_mutex->locked)
219         {
220             p_mutex->locked = true;
221             ret = 0;
222         }
223         vlc_mutex_unlock (&super_mutex);
224         return ret;
225     }
226
227     return DosRequestMutexSem( p_mutex->hmtx, 0 ) ? EBUSY : 0;
228 }
229
230 void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
231 {
232     if (!p_mutex->dynamic)
233     {   /* static mutexes */
234         assert (p_mutex != &super_mutex); /* this one cannot be static */
235
236         vlc_mutex_lock (&super_mutex);
237         assert (p_mutex->locked);
238         p_mutex->locked = false;
239         if (p_mutex->contention)
240             vlc_cond_broadcast (&super_variable);
241         vlc_mutex_unlock (&super_mutex);
242         return;
243     }
244
245     DosReleaseMutexSem( p_mutex->hmtx );
246 }
247
248 /*** Condition variables ***/
249 enum
250 {
251     CLOCK_REALTIME=0, /* must be zero for VLC_STATIC_COND */
252     CLOCK_MONOTONIC,
253 };
254
255 static void vlc_cond_init_common (vlc_cond_t *p_condvar, unsigned clock)
256 {
257     /* Create a manual-reset event (manual reset is needed for broadcast). */
258     if (DosCreateEventSem (NULL, &p_condvar->hev, 0, FALSE))
259         abort();
260     p_condvar->clock = clock;
261 }
262
263 void vlc_cond_init (vlc_cond_t *p_condvar)
264 {
265     vlc_cond_init_common (p_condvar, CLOCK_MONOTONIC);
266 }
267
268 void vlc_cond_init_daytime (vlc_cond_t *p_condvar)
269 {
270     vlc_cond_init_common (p_condvar, CLOCK_REALTIME);
271 }
272
273 void vlc_cond_destroy (vlc_cond_t *p_condvar)
274 {
275     DosCloseEventSem( p_condvar->hev );
276 }
277
278 void vlc_cond_signal (vlc_cond_t *p_condvar)
279 {
280     if (!p_condvar->hev)
281         return;
282
283     /* This is suboptimal but works. */
284     vlc_cond_broadcast (p_condvar);
285 }
286
287 void vlc_cond_broadcast (vlc_cond_t *p_condvar)
288 {
289     if (!p_condvar->hev)
290         return;
291
292     /* Wake all threads up (as the event HANDLE has manual reset) */
293     DosPostEventSem( p_condvar->hev );
294 }
295
296 void vlc_cond_wait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex)
297 {
298     ULONG ulPost;
299     ULONG rc;
300
301     if (!p_condvar->hev)
302     {   /* FIXME FIXME FIXME */
303         msleep (50000);
304         return;
305     }
306
307     do
308     {
309         vlc_testcancel();
310
311         vlc_mutex_unlock (p_mutex);
312         rc = vlc_WaitForSingleObject( p_condvar->hev, SEM_INDEFINITE_WAIT );
313         vlc_mutex_lock (p_mutex);
314     } while( rc == ERROR_INTERRUPT );
315
316     DosResetEventSem( p_condvar->hev, &ulPost );
317 }
318
319 int vlc_cond_timedwait (vlc_cond_t *p_condvar, vlc_mutex_t *p_mutex,
320                         mtime_t deadline)
321 {
322     ULONG   ulTimeout;
323     ULONG   ulPost;
324     ULONG   rc;
325
326     if (!p_condvar->hev)
327     {   /* FIXME FIXME FIXME */
328         msleep (50000);
329         return;
330     }
331
332     do
333     {
334         vlc_testcancel();
335
336         mtime_t total;
337         switch (p_condvar->clock)
338         {
339             case CLOCK_REALTIME: /* FIXME? sub-second precision */
340                 total = CLOCK_FREQ * time (NULL);
341                 break;
342             default:
343                 assert (p_condvar->clock == CLOCK_MONOTONIC);
344                 total = mdate();
345                 break;
346         }
347         total = (deadline - total) / 1000;
348         if( total < 0 )
349             total = 0;
350
351         ulTimeout = ( total > 0x7fffffff ) ? 0x7fffffff : total;
352
353         vlc_mutex_unlock (p_mutex);
354         rc = vlc_WaitForSingleObject( p_condvar->hev, ulTimeout );
355         vlc_mutex_lock (p_mutex);
356     } while( rc == ERROR_INTERRUPT );
357
358     DosResetEventSem( p_condvar->hev, &ulPost );
359
360     return rc ? ETIMEDOUT : 0;
361 }
362
363 /*** Thread-specific variables (TLS) ***/
364 struct vlc_threadvar
365 {
366     PULONG                id;
367     void                (*destroy) (void *);
368     struct vlc_threadvar *prev;
369     struct vlc_threadvar *next;
370 } *vlc_threadvar_last = NULL;
371
372 int vlc_threadvar_create (vlc_threadvar_t *p_tls, void (*destr) (void *))
373 {
374     ULONG rc;
375
376     struct vlc_threadvar *var = malloc (sizeof (*var));
377     if (unlikely(var == NULL))
378         return errno;
379
380     rc = DosAllocThreadLocalMemory( 1, &var->id );
381     if( rc )
382     {
383         free (var);
384         return EAGAIN;
385     }
386
387     var->destroy = destr;
388     var->next = NULL;
389     *p_tls = var;
390
391     vlc_mutex_lock (&super_mutex);
392     var->prev = vlc_threadvar_last;
393     if (var->prev)
394         var->prev->next = var;
395
396     vlc_threadvar_last = var;
397     vlc_mutex_unlock (&super_mutex);
398     return 0;
399 }
400
401 void vlc_threadvar_delete (vlc_threadvar_t *p_tls)
402 {
403     struct vlc_threadvar *var = *p_tls;
404
405     vlc_mutex_lock (&super_mutex);
406     if (var->prev != NULL)
407         var->prev->next = var->next;
408
409     if (var->next != NULL)
410         var->next->prev = var->prev;
411     else
412         vlc_threadvar_last = var->prev;
413
414     vlc_mutex_unlock (&super_mutex);
415
416     DosFreeThreadLocalMemory( var->id );
417     free (var);
418 }
419
420 int vlc_threadvar_set (vlc_threadvar_t key, void *value)
421 {
422     *key->id = ( ULONG )value;
423     return 0;
424 }
425
426 void *vlc_threadvar_get (vlc_threadvar_t key)
427 {
428     return ( void * )*key->id;
429 }
430
431
432 /*** Threads ***/
433 void vlc_threads_setup (libvlc_int_t *p_libvlc)
434 {
435     (void) p_libvlc;
436 }
437
438 static void vlc_thread_cleanup (struct vlc_thread *th)
439 {
440     vlc_threadvar_t key;
441
442 retry:
443     /* TODO: use RW lock or something similar */
444     vlc_mutex_lock (&super_mutex);
445     for (key = vlc_threadvar_last; key != NULL; key = key->prev)
446     {
447         void *value = vlc_threadvar_get (key);
448         if (value != NULL && key->destroy != NULL)
449         {
450             vlc_mutex_unlock (&super_mutex);
451             vlc_threadvar_set (key, NULL);
452             key->destroy (value);
453             goto retry;
454         }
455     }
456     vlc_mutex_unlock (&super_mutex);
457
458     if (th->detached)
459     {
460         DosCloseEventSem (th->cancel_event);
461         DosCloseEventSem (th->done_event );
462         free (th);
463     }
464 }
465
466 static void vlc_entry( void *p )
467 {
468     struct vlc_thread *th = p;
469
470     vlc_threadvar_set (thread_key, th);
471     th->killable = true;
472     th->data = th->entry (th->data);
473     DosPostEventSem( th->done_event );
474     vlc_thread_cleanup (th);
475 }
476
477 static int vlc_clone_attr (vlc_thread_t *p_handle, bool detached,
478                            void *(*entry) (void *), void *data, int priority)
479 {
480     struct vlc_thread *th = malloc (sizeof (*th));
481     if (unlikely(th == NULL))
482         return ENOMEM;
483     th->entry = entry;
484     th->data = data;
485     th->detached = detached;
486     th->killable = false; /* not until vlc_entry() ! */
487     th->killed = false;
488     th->cleaners = NULL;
489
490     if( DosCreateEventSem (NULL, &th->cancel_event, 0, FALSE))
491         goto error;
492     if( DosCreateEventSem (NULL, &th->done_event, 0, FALSE))
493         goto error;
494
495     th->tid = _beginthread (vlc_entry, NULL, 1024 * 1024, th);
496     if((int)th->tid == -1)
497         goto error;
498
499     if (p_handle != NULL)
500         *p_handle = th;
501
502     if (priority)
503         DosSetPriority(PRTYS_THREAD,
504                        HIBYTE(priority),
505                        LOBYTE(priority),
506                        th->tid);
507
508     return 0;
509
510 error:
511     DosCloseEventSem (th->cancel_event);
512     DosCloseEventSem (th->done_event);
513     free (th);
514
515     return ENOMEM;
516 }
517
518 int vlc_clone (vlc_thread_t *p_handle, void *(*entry) (void *),
519                 void *data, int priority)
520 {
521     return vlc_clone_attr (p_handle, false, entry, data, priority);
522 }
523
524 void vlc_join (vlc_thread_t th, void **result)
525 {
526     ULONG rc;
527
528     do
529     {
530         vlc_testcancel();
531         rc = vlc_WaitForSingleObject( th->done_event, SEM_INDEFINITE_WAIT );
532     } while( rc == ERROR_INTERRUPT );
533
534     if (result != NULL)
535         *result = th->data;
536
537     DosCloseEventSem( th->cancel_event );
538     DosCloseEventSem( th->done_event );
539
540     free( th );
541 }
542
543 int vlc_clone_detach (vlc_thread_t *p_handle, void *(*entry) (void *),
544                       void *data, int priority)
545 {
546     vlc_thread_t th;
547     if (p_handle == NULL)
548         p_handle = &th;
549
550     return vlc_clone_attr (p_handle, true, entry, data, priority);
551 }
552
553 int vlc_set_priority (vlc_thread_t th, int priority)
554 {
555     if (DosSetPriority(PRTYS_THREAD,
556                        HIBYTE(priority),
557                        LOBYTE(priority),
558                        th->tid))
559         return VLC_EGENERIC;
560     return VLC_SUCCESS;
561 }
562
563 /*** Thread cancellation ***/
564
565 /* APC procedure for thread cancellation */
566 static void vlc_cancel_self (PVOID self)
567 {
568     struct vlc_thread *th = self;
569
570     if (likely(th != NULL))
571         th->killed = true;
572 }
573
574 void vlc_cancel (vlc_thread_t thread_id)
575 {
576     DosPostEventSem( thread_id->cancel_event );
577 }
578
579 int vlc_savecancel (void)
580 {
581     int state;
582
583     struct vlc_thread *th = vlc_threadvar_get (thread_key);
584     if (th == NULL)
585         return false; /* Main thread - cannot be cancelled anyway */
586
587     state = th->killable;
588     th->killable = false;
589     return state;
590 }
591
592 void vlc_restorecancel (int state)
593 {
594     struct vlc_thread *th = vlc_threadvar_get (thread_key);
595     assert (state == false || state == true);
596
597     if (th == NULL)
598         return; /* Main thread - cannot be cancelled anyway */
599
600     assert (!th->killable);
601     th->killable = state != 0;
602 }
603
604 void vlc_testcancel (void)
605 {
606     struct vlc_thread *th = vlc_threadvar_get (thread_key);
607     if (th == NULL)
608         return; /* Main thread - cannot be cancelled anyway */
609
610     /* This check is needed for the case that vlc_cancel() is followed by
611      * vlc_testcancel() without any cancellation point */
612     if( DosWaitEventSem( th->cancel_event, 0 ) == NO_ERROR )
613         vlc_cancel_self( th );
614
615     if (th->killable && th->killed)
616     {
617         for (vlc_cleanup_t *p = th->cleaners; p != NULL; p = p->next)
618              p->proc (p->data);
619
620         DosPostEventSem( th->done_event );
621         th->data = NULL; /* TODO: special value? */
622         vlc_thread_cleanup (th);
623         _endthread();
624     }
625 }
626
627 void vlc_control_cancel (int cmd, ...)
628 {
629     /* NOTE: This function only modifies thread-specific data, so there is no
630      * need to lock anything. */
631     va_list ap;
632
633     struct vlc_thread *th = vlc_threadvar_get (thread_key);
634     if (th == NULL)
635         return; /* Main thread - cannot be cancelled anyway */
636
637     va_start (ap, cmd);
638     switch (cmd)
639     {
640         case VLC_CLEANUP_PUSH:
641         {
642             /* cleaner is a pointer to the caller stack, no need to allocate
643              * and copy anything. As a nice side effect, this cannot fail. */
644             vlc_cleanup_t *cleaner = va_arg (ap, vlc_cleanup_t *);
645             cleaner->next = th->cleaners;
646             th->cleaners = cleaner;
647             break;
648         }
649
650         case VLC_CLEANUP_POP:
651         {
652             th->cleaners = th->cleaners->next;
653             break;
654         }
655     }
656     va_end (ap);
657 }
658
659 #define Q2LL( q )   ( *( long long * )&( q ))
660
661 /*** Clock ***/
662 mtime_t mdate (void)
663 {
664     /* We don't need the real date, just the value of a high precision timer */
665     QWORD counter;
666     ULONG freq;
667     if (DosTmrQueryTime(&counter) || DosTmrQueryFreq(&freq))
668         abort();
669
670     /* Convert to from (1/freq) to microsecond resolution */
671     /* We need to split the division to avoid 63-bits overflow */
672     lldiv_t d = lldiv (Q2LL(counter), freq);
673
674     return (d.quot * 1000000) + ((d.rem * 1000000) / freq);
675 }
676
677 #undef mwait
678 void mwait (mtime_t deadline)
679 {
680     mtime_t delay;
681
682     vlc_testcancel();
683     while ((delay = (deadline - mdate())) > 0)
684     {
685         delay /= 1000;
686         if (unlikely(delay > 0x7fffffff))
687             delay = 0x7fffffff;
688         vlc_Sleep (delay);
689         vlc_testcancel();
690     }
691 }
692
693 #undef msleep
694 void msleep (mtime_t delay)
695 {
696     mwait (mdate () + delay);
697 }
698
699 /*** Timers ***/
700 struct vlc_timer
701 {
702     TID    tid;
703     HEV    hev;
704     HTIMER htimer;
705     ULONG  interval;
706     bool   quit;
707     void (*func) (void *);
708     void  *data;
709 };
710
711 static void vlc_timer_do (void *arg)
712 {
713     struct vlc_timer *timer = arg;
714
715     while (1)
716     {
717         ULONG count;
718
719         DosWaitEventSem (timer->hev, SEM_INDEFINITE_WAIT);
720         DosResetEventSem (timer->hev, &count);
721
722         if (timer->quit)
723             break;
724
725         timer->func (timer->data);
726
727         if (timer->interval)
728             DosAsyncTimer (timer->interval, (HSEM)timer->hev, &timer->htimer);
729     }
730 }
731
732 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
733 {
734     struct vlc_timer *timer = malloc (sizeof (*timer));
735
736     if (timer == NULL)
737         return ENOMEM;
738
739     timer->func = func;
740     timer->data = data;
741
742     DosCreateEventSem (NULL, &timer->hev, DC_SEM_SHARED, FALSE);
743     timer->htimer = NULLHANDLE;
744     timer->interval = 0;
745     timer->quit = false;
746     timer->tid  = _beginthread (vlc_timer_do, NULL, 1024 * 1024, timer);
747
748     *id = timer;
749     return 0;
750 }
751
752 void vlc_timer_destroy (vlc_timer_t timer)
753 {
754     if (timer->htimer != NULLHANDLE)
755         DosStopTimer (timer->htimer);
756
757     timer->quit = true;
758     DosPostEventSem (timer->hev);
759     DosWaitThread (&timer->tid, DCWW_WAIT);
760     DosCloseEventSem (timer->hev);
761
762     free (timer);
763 }
764
765 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
766                          mtime_t value, mtime_t interval)
767 {
768     if (timer->htimer != NULLHANDLE)
769     {
770         DosStopTimer (timer->htimer);
771         timer->htimer = NULLHANDLE;
772         timer->interval = 0;
773     }
774
775     if (value == 0)
776         return; /* Disarm */
777
778     if (absolute)
779         value -= mdate ();
780     value = (value + 999) / 1000;
781     interval = (interval + 999) / 1000;
782
783     timer->interval = interval;
784     if (DosAsyncTimer (value, (HSEM)timer->hev, &timer->htimer))
785         abort ();
786 }
787
788 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
789 {
790     (void)timer;
791     return 0;
792 }
793
794 /*** CPU ***/
795 unsigned vlc_GetCPUCount (void)
796 {
797     ULONG numprocs = 1;
798
799     DosQuerySysInfo(QSV_NUMPROCESSORS, QSV_NUMPROCESSORS,
800                     &numprocs, sizeof(numprocs));
801
802     return numprocs;
803 }