]> git.sesse.net Git - vlc/blob - src/misc/fifo.c
10749e9a5c916ddea7062e4d976cbd0260f753ab
[vlc] / src / misc / fifo.c
1 /*****************************************************************************
2  * fifo.c: FIFO management functions
3  *****************************************************************************
4  * Copyright (C) 2003-2004 VLC authors and VideoLAN
5  * Copyright (C) 2007-2015 RĂ©mi Denis-Courmont
6  *
7  * Authors: Laurent Aimar <fenrir@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <assert.h>
29 #include <stdlib.h>
30
31 #include <vlc_common.h>
32 #include <vlc_block.h>
33 #include "libvlc.h"
34
35 /**
36  * @section Thread-safe block queue functions
37  */
38
39 /**
40  * Internal state for block queues
41  */
42 struct block_fifo_t
43 {
44     vlc_mutex_t         lock;                         /* fifo data lock */
45     vlc_cond_t          wait;      /**< Wait for data */
46     vlc_cond_t          wait_room; /**< Wait for queue depth to shrink */
47
48     block_t             *p_first;
49     block_t             **pp_last;
50     size_t              i_depth;
51     size_t              i_size;
52     bool          b_force_wake;
53 };
54
55 /**
56  * Locks a block FIFO. No more than one thread can lock the FIFO at any given
57  * time, and no other thread can modify the FIFO while it is locked.
58  * vlc_fifo_Unlock() releases the lock.
59  *
60  * @note If the FIFO is already locked by another thread, this function waits.
61  * This function is not a cancellation point.
62  *
63  * @warning Recursively locking a single FIFO is undefined. Locking more than
64  * one FIFO at a time may lead to lock inversion; mind the locking order.
65  */
66 void vlc_fifo_Lock(vlc_fifo_t *fifo)
67 {
68     vlc_mutex_lock(&fifo->lock);
69 }
70
71 /**
72  * Unlocks a block FIFO previously locked by block_FifoLock().
73  *
74  * @note This function is not a cancellation point.
75  *
76  * @warning Unlocking a FIFO not locked by the calling thread is undefined.
77  */
78 void vlc_fifo_Unlock(vlc_fifo_t *fifo)
79 {
80     vlc_mutex_unlock(&fifo->lock);
81 }
82
83 /**
84  * Wakes up one thread waiting on the FIFO, if any.
85  *
86  * @note This function is not a cancellation point.
87  *
88  * @warning For race-free operations, the FIFO should be locked by the calling
89  * thread. The function can be called on a unlocked FIFO however.
90  */
91 void vlc_fifo_Signal(vlc_fifo_t *fifo)
92 {
93     vlc_cond_signal(&fifo->wait);
94 }
95
96 /**
97  * Atomically unlocks the FIFO and waits until one thread signals the FIFO,
98  * then locks the FIFO again. A signal can be sent by queueing a block to the
99  * previously empty FIFO or by calling vlc_fifo_Signal() directly.
100  * This function may also return spuriously at any moment.
101  *
102  * @note This function is a cancellation point. In case of cancellation, the
103  * the FIFO will be locked before cancellation cleanup handlers are processed.
104  */
105 void vlc_fifo_Wait(vlc_fifo_t *fifo)
106 {
107     vlc_fifo_WaitCond(fifo, &fifo->wait);
108 }
109
110 void vlc_fifo_WaitCond(vlc_fifo_t *fifo, vlc_cond_t *condvar)
111 {
112     vlc_cond_wait(condvar, &fifo->lock);
113 }
114
115 /**
116  * Checks how many blocks are queued in a locked FIFO.
117  *
118  * @note This function is not cancellation point.
119  *
120  * @warning The FIFO must be locked by the calling thread using
121  * vlc_fifo_Lock(). Otherwise behaviour is undefined.
122  *
123  * @return the number of blocks in the FIFO (zero if it is empty)
124  */
125 size_t vlc_fifo_GetCount(const vlc_fifo_t *fifo)
126 {
127     return fifo->i_depth;
128 }
129
130 /**
131  * Checks how many bytes are queued in a locked FIFO.
132  *
133  * @note This function is not cancellation point.
134  *
135  * @warning The FIFO must be locked by the calling thread using
136  * vlc_fifo_Lock(). Otherwise behaviour is undefined.
137  *
138  * @return the total number of bytes
139  *
140  * @note Zero bytes does not necessarily mean that the FIFO is empty since
141  * a block could contain zero bytes. Use vlc_fifo_GetCount() to determine if
142  * a FIFO is empty.
143  */
144 size_t vlc_fifo_GetBytes(const vlc_fifo_t *fifo)
145 {
146     return fifo->i_size;
147 }
148
149 /**
150  * Queues a linked-list of blocks into a locked FIFO.
151  *
152  * @param block the head of the list of blocks
153  *              (if NULL, this function has no effects)
154  *
155  * @note This function is not a cancellation point.
156  *
157  * @warning The FIFO must be locked by the calling thread using
158  * vlc_fifo_Lock(). Otherwise behaviour is undefined.
159  */
160 void vlc_fifo_QueueUnlocked(block_fifo_t *fifo, block_t *block)
161 {
162     vlc_assert_locked(&fifo->lock);
163     assert(*(fifo->pp_last) == NULL);
164
165     *(fifo->pp_last) = block;
166
167     while (block != NULL)
168     {
169         fifo->pp_last = &block->p_next;
170         fifo->i_depth++;
171         fifo->i_size += block->i_size;
172
173         block = block->p_next;
174     }
175
176     vlc_cond_signal(&fifo->wait);
177 }
178
179 /**
180  * Dequeues the first block from a locked FIFO, if any.
181  *
182  * @note This function is not a cancellation point.
183  *
184  * @warning The FIFO must be locked by the calling thread using
185  * vlc_fifo_Lock(). Otherwise behaviour is undefined.
186  *
187  * @return the first block in the FIFO or NULL if the FIFO is empty
188  */
189 block_t *vlc_fifo_DequeueUnlocked(block_fifo_t *fifo)
190 {
191     vlc_assert_locked(&fifo->lock);
192
193     block_t *block = fifo->p_first;
194
195     if (block == NULL)
196         return NULL; /* Nothing to do */
197
198     fifo->p_first = block->p_next;
199     if (block->p_next == NULL)
200         fifo->pp_last = &fifo->p_first;
201     block->p_next = NULL;
202
203     assert(fifo->i_depth > 0);
204     fifo->i_depth--;
205     assert(fifo->i_size >= block->i_buffer);
206     fifo->i_size -= block->i_buffer;
207
208     /* We don't know how many threads can queue new packets now. */
209     vlc_cond_broadcast(&fifo->wait_room);
210
211     return block;
212 }
213
214 /**
215  * Dequeues the all blocks from a locked FIFO. This is equivalent to calling
216  * vlc_fifo_DequeueUnlocked() repeatedly until the FIFO is emptied, but this
217  * function is faster.
218  *
219  * @note This function is not a cancellation point.
220  *
221  * @warning The FIFO must be locked by the calling thread using
222  * vlc_fifo_Lock(). Otherwise behaviour is undefined.
223  *
224  * @return a linked-list of all blocks in the FIFO (possibly NULL)
225  */
226 block_t *vlc_fifo_DequeueAllUnlocked(block_fifo_t *fifo)
227 {
228     vlc_assert_locked(&fifo->lock);
229
230     block_t *block = fifo->p_first;
231
232     fifo->p_first = NULL;
233     fifo->pp_last = &fifo->p_first;
234     fifo->i_depth = 0;
235     fifo->i_size = 0;
236
237     /* We don't know how many threads can queue new packets now. */
238     vlc_cond_broadcast(&fifo->wait_room);
239
240     return block;
241 }
242
243
244 /**
245  * Creates a thread-safe FIFO queue of blocks.
246  * See also block_FifoPut() and block_FifoGet().
247  * @return the FIFO or NULL on memory error
248  */
249 block_fifo_t *block_FifoNew( void )
250 {
251     block_fifo_t *p_fifo = malloc( sizeof( block_fifo_t ) );
252     if( !p_fifo )
253         return NULL;
254
255     vlc_mutex_init( &p_fifo->lock );
256     vlc_cond_init( &p_fifo->wait );
257     vlc_cond_init( &p_fifo->wait_room );
258     p_fifo->p_first = NULL;
259     p_fifo->pp_last = &p_fifo->p_first;
260     p_fifo->i_depth = p_fifo->i_size = 0;
261     p_fifo->b_force_wake = false;
262
263     return p_fifo;
264 }
265
266 /**
267  * Destroys a FIFO created by block_FifoNew().
268  * Any queued blocks are also destroyed.
269  */
270 void block_FifoRelease( block_fifo_t *p_fifo )
271 {
272     block_ChainRelease( p_fifo->p_first );
273     vlc_cond_destroy( &p_fifo->wait_room );
274     vlc_cond_destroy( &p_fifo->wait );
275     vlc_mutex_destroy( &p_fifo->lock );
276     free( p_fifo );
277 }
278
279 /**
280  * Clears all blocks in a FIFO.
281  */
282 void block_FifoEmpty(block_fifo_t *fifo)
283 {
284     block_t *block;
285
286     vlc_fifo_Lock(fifo);
287     block = vlc_fifo_DequeueAllUnlocked(fifo);
288     vlc_fifo_Unlock(fifo);
289     block_ChainRelease(block);
290 }
291
292 /**
293  * Wait until the FIFO gets below a certain size (if needed).
294  *
295  * Note that if more than one thread writes to the FIFO, you cannot assume that
296  * the FIFO is actually below the requested size upon return (since another
297  * thread could have refilled it already). This is typically not an issue, as
298  * this function is meant for (relaxed) congestion control.
299  *
300  * This function may be a cancellation point and it is cancel-safe.
301  *
302  * @param fifo queue to wait on
303  * @param max_depth wait until the queue has no more than this many blocks
304  *                  (use SIZE_MAX to ignore this constraint)
305  * @param max_size wait until the queue has no more than this many bytes
306  *                  (use SIZE_MAX to ignore this constraint)
307  * @return nothing.
308  */
309 void block_FifoPace (block_fifo_t *fifo, size_t max_depth, size_t max_size)
310 {
311     vlc_testcancel ();
312
313     vlc_mutex_lock (&fifo->lock);
314     while ((fifo->i_depth > max_depth) || (fifo->i_size > max_size))
315     {
316          mutex_cleanup_push (&fifo->lock);
317          vlc_cond_wait (&fifo->wait_room, &fifo->lock);
318          vlc_cleanup_pop ();
319     }
320     vlc_mutex_unlock (&fifo->lock);
321 }
322
323 /**
324  * Immediately queue one block at the end of a FIFO.
325  * @param fifo queue
326  * @param block head of a block list to queue (may be NULL)
327  */
328 void block_FifoPut(block_fifo_t *fifo, block_t *block)
329 {
330     vlc_fifo_Lock(fifo);
331     vlc_fifo_QueueUnlocked(fifo, block);
332     vlc_fifo_Unlock(fifo);
333 }
334
335 void block_FifoWake( block_fifo_t *p_fifo )
336 {
337     vlc_mutex_lock( &p_fifo->lock );
338     if( p_fifo->p_first == NULL )
339         p_fifo->b_force_wake = true;
340     vlc_cond_broadcast( &p_fifo->wait );
341     vlc_mutex_unlock( &p_fifo->lock );
342 }
343
344 /**
345  * Dequeue the first block from the FIFO. If necessary, wait until there is
346  * one block in the queue. This function is (always) cancellation point.
347  *
348  * @return a valid block, or NULL if block_FifoWake() was called.
349  */
350 block_t *block_FifoGet(block_fifo_t *fifo)
351 {
352     block_t *block;
353
354     vlc_testcancel();
355
356     vlc_fifo_Lock(fifo);
357     while (vlc_fifo_IsEmpty(fifo) && !fifo->b_force_wake)
358     {
359         vlc_fifo_CleanupPush(fifo);
360         vlc_fifo_Wait(fifo);
361         vlc_cleanup_pop();
362     }
363     fifo->b_force_wake = false;
364     block = vlc_fifo_DequeueUnlocked(fifo);
365     vlc_fifo_Unlock(fifo);
366
367     return block;
368 }
369
370 /**
371  * Peeks the first block in the FIFO.
372  * If necessary, wait until there is one block.
373  * This function is (always) a cancellation point.
374  *
375  * @warning This function leaves the block in the FIFO.
376  * You need to protect against concurrent threads who could dequeue the block.
377  * Preferrably, there should be only one thread reading from the FIFO.
378  *
379  * @return a valid block.
380  */
381 block_t *block_FifoShow( block_fifo_t *p_fifo )
382 {
383     block_t *b;
384
385     vlc_testcancel( );
386
387     vlc_mutex_lock( &p_fifo->lock );
388     mutex_cleanup_push( &p_fifo->lock );
389
390     while( p_fifo->p_first == NULL )
391         vlc_cond_wait( &p_fifo->wait, &p_fifo->lock );
392
393     b = p_fifo->p_first;
394
395     vlc_cleanup_run ();
396     return b;
397 }
398
399 /* FIXME: not (really) thread-safe */
400 size_t block_FifoSize (block_fifo_t *fifo)
401 {
402     size_t size;
403
404     vlc_mutex_lock (&fifo->lock);
405     size = fifo->i_size;
406     vlc_mutex_unlock (&fifo->lock);
407     return size;
408 }
409
410 /* FIXME: not (really) thread-safe */
411 size_t block_FifoCount (block_fifo_t *fifo)
412 {
413     size_t depth;
414
415     vlc_mutex_lock (&fifo->lock);
416     depth = fifo->i_depth;
417     vlc_mutex_unlock (&fifo->lock);
418     return depth;
419 }