]> git.sesse.net Git - vlc/blob - src/posix/timer.c
Fix Metacube header handling with multiple header blocks.
[vlc] / src / posix / timer.c
1 /*****************************************************************************
2  * timer.c: simple threaded timer
3  *****************************************************************************
4  * Copyright (C) 2009-2012 RĂ©mi Denis-Courmont
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19  *****************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #include <stdlib.h>
26 #include <errno.h>
27 #include <assert.h>
28
29 #include <vlc_common.h>
30 #include <vlc_atomic.h>
31
32 /*
33  * POSIX timers are essentially unusable from a library: there provide no safe
34  * way to ensure that a timer has no pending/ongoing iteration. Furthermore,
35  * they typically require one thread per timer plus one thread per iteration,
36  * which is inefficient and overkill (unless you need multiple iteration
37  * of the same timer concurrently).
38  * Thus, this is a generic manual implementation of timers using a thread.
39  */
40
41 struct vlc_timer
42 {
43     vlc_thread_t thread;
44     vlc_cond_t   reschedule;
45     vlc_mutex_t  lock;
46     void       (*func) (void *);
47     void        *data;
48     mtime_t      value, interval;
49     atomic_uint  overruns;
50 };
51
52 VLC_NORETURN
53 static void *vlc_timer_thread (void *data)
54 {
55     struct vlc_timer *timer = data;
56
57     vlc_mutex_lock (&timer->lock);
58     mutex_cleanup_push (&timer->lock);
59
60     for (;;)
61     {
62         while (timer->value == 0)
63             vlc_cond_wait (&timer->reschedule, &timer->lock);
64
65         if (vlc_cond_timedwait (&timer->reschedule, &timer->lock,
66                                 timer->value) == 0)
67             continue;
68         if (timer->interval == 0)
69             timer->value = 0; /* disarm */
70         vlc_mutex_unlock (&timer->lock);
71
72         int canc = vlc_savecancel ();
73         timer->func (timer->data);
74         vlc_restorecancel (canc);
75
76         mtime_t now = mdate ();
77         unsigned misses;
78
79         vlc_mutex_lock (&timer->lock);
80         if (timer->interval == 0)
81             continue;
82
83         misses = (now - timer->value) / timer->interval;
84         timer->value += timer->interval;
85         /* Try to compensate for one miss (mwait() will return immediately)
86          * but no more. Otherwise, we might busy loop, after extended periods
87          * without scheduling (suspend, SIGSTOP, RT preemption, ...). */
88         if (misses > 1)
89         {
90             misses--;
91             timer->value += misses * timer->interval;
92             atomic_fetch_add_explicit (&timer->overruns, misses,
93                                        memory_order_relaxed);
94         }
95     }
96
97     vlc_cleanup_pop ();
98     vlc_assert_unreachable ();
99 }
100
101 /**
102  * Initializes an asynchronous timer.
103  * @warning Asynchronous timers are processed from an unspecified thread.
104  * Multiple occurences of a single interval timer are serialized; they cannot
105  * run concurrently.
106  *
107  * @param id pointer to timer to be initialized
108  * @param func function that the timer will call
109  * @param data parameter for the timer function
110  * @return 0 on success, a system error code otherwise.
111  */
112 int vlc_timer_create (vlc_timer_t *id, void (*func) (void *), void *data)
113 {
114     struct vlc_timer *timer = malloc (sizeof (*timer));
115
116     if (unlikely(timer == NULL))
117         return ENOMEM;
118     vlc_mutex_init (&timer->lock);
119     vlc_cond_init (&timer->reschedule);
120     assert (func);
121     timer->func = func;
122     timer->data = data;
123     timer->value = 0;
124     timer->interval = 0;
125     atomic_init(&timer->overruns, 0);
126
127     if (vlc_clone (&timer->thread, vlc_timer_thread, timer,
128                    VLC_THREAD_PRIORITY_INPUT))
129     {
130         vlc_cond_destroy (&timer->reschedule);
131         vlc_mutex_destroy (&timer->lock);
132         free (timer);
133         return ENOMEM;
134     }
135
136     *id = timer;
137     return 0;
138 }
139
140 /**
141  * Destroys an initialized timer. If needed, the timer is first disarmed.
142  * This function is undefined if the specified timer is not initialized.
143  *
144  * @warning This function <b>must</b> be called before the timer data can be
145  * freed and before the timer callback function can be unloaded.
146  *
147  * @param timer timer to destroy
148  */
149 void vlc_timer_destroy (vlc_timer_t timer)
150 {
151     vlc_cancel (timer->thread);
152     vlc_join (timer->thread, NULL);
153     vlc_cond_destroy (&timer->reschedule);
154     vlc_mutex_destroy (&timer->lock);
155     free (timer);
156 }
157
158 /**
159  * Arm or disarm an initialized timer.
160  * This functions overrides any previous call to itself.
161  *
162  * @note A timer can fire later than requested due to system scheduling
163  * limitations. An interval timer can fail to trigger sometimes, either because
164  * the system is busy or suspended, or because a previous iteration of the
165  * timer is still running. See also vlc_timer_getoverrun().
166  *
167  * @param timer initialized timer
168  * @param absolute the timer value origin is the same as mdate() if true,
169  *                 the timer value is relative to now if false.
170  * @param value zero to disarm the timer, otherwise the initial time to wait
171  *              before firing the timer.
172  * @param interval zero to fire the timer just once, otherwise the timer
173  *                 repetition interval.
174  */
175 void vlc_timer_schedule (vlc_timer_t timer, bool absolute,
176                          mtime_t value, mtime_t interval)
177 {
178     if (!absolute && value != 0)
179         value += mdate();
180
181     vlc_mutex_lock (&timer->lock);
182     timer->value = value;
183     timer->interval = interval;
184     vlc_cond_signal (&timer->reschedule);
185     vlc_mutex_unlock (&timer->lock);
186 }
187
188 /**
189  * Fetch and reset the overrun counter for a timer.
190  * @param timer initialized timer
191  * @return the timer overrun counter, i.e. the number of times that the timer
192  * should have run but did not since the last actual run. If all is well, this
193  * is zero.
194  */
195 unsigned vlc_timer_getoverrun (vlc_timer_t timer)
196 {
197     return atomic_exchange_explicit (&timer->overruns, 0,
198                                      memory_order_relaxed);
199 }