vlc | branch: master | Filip Roséen <[email protected]> | Tue Oct 11 01:36:25 2016 +0200| [537fd3b0501a47242f3d06f90d344dace465a684] | committer: Hugo Beauzée-Luyssen
playlist/background_worker: introduce background-worker utility This added utility will make it easier to handle a queue of tasks that is to be finished in the order received. It allows for users of the utility to focus on the end-goal instead of having to deal with synchronization issues in terms of task-queue handling. Signed-off-by: Hugo Beauzée-Luyssen <[email protected]> > http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=537fd3b0501a47242f3d06f90d344dace465a684 --- src/Makefile.am | 2 + src/misc/background_worker.c | 235 +++++++++++++++++++++++++++++++++++++++++++ src/misc/background_worker.h | 182 +++++++++++++++++++++++++++++++++ 3 files changed, 419 insertions(+) diff --git a/src/Makefile.am b/src/Makefile.am index 1392248..954322b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -310,6 +310,8 @@ libvlccore_la_SOURCES = \ text/filesystem.c \ text/iso_lang.c \ text/iso-639_def.h \ + misc/background_worker.c \ + misc/background_worker.h \ misc/md5.c \ misc/probe.c \ misc/rand.c \ diff --git a/src/misc/background_worker.c b/src/misc/background_worker.c new file mode 100644 index 0000000..8009a60 --- /dev/null +++ b/src/misc/background_worker.c @@ -0,0 +1,235 @@ +/***************************************************************************** + * Copyright (C) 2017 VLC authors and VideoLAN + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <vlc_common.h> +#include <vlc_threads.h> +#include <vlc_arrays.h> + +#include "libvlc.h" +#include "background_worker.h" + +struct bg_queued_item { + void* id; /**< id associated with entity */ + void* entity; /**< the entity to process */ + int timeout; /**< timeout duration in microseconds */ +}; + +struct background_worker { + void* owner; + struct background_worker_config conf; + + struct { + bool probe_request; /**< true if a probe is requested */ + vlc_mutex_t lock; /**< acquire to inspect members that follow */ + vlc_cond_t wait; /**< wait for update in terms of head */ + mtime_t deadline; /**< deadline of the current task */ + void* id; /**< id of the current task */ + bool active; /**< true if there is an active thread */ + } head; + + struct { + vlc_mutex_t lock; /**< acquire to inspect members that follow */ + vlc_array_t data; /**< queue of pending entities to process */ + } tail; +}; + +static void* Thread( void* data ) +{ + struct background_worker* worker = data; + + for( ;; ) + { + struct bg_queued_item* item = NULL; + void* handle; + + vlc_mutex_lock( &worker->tail.lock ); + { + if( vlc_array_count( &worker->tail.data ) ) + { + item = vlc_array_item_at_index( &worker->tail.data, 0 ); + handle = NULL; + + vlc_array_remove( &worker->tail.data, 0 ); + } + + vlc_mutex_lock( &worker->head.lock ); + { + worker->head.deadline = INT64_MAX; + worker->head.active = item != NULL; + worker->head.id = item ? item->id : NULL; + + if( item && item->timeout > 0 ) + worker->head.deadline = mdate() + item->timeout * 1000; + } + vlc_cond_broadcast( &worker->head.wait ); + vlc_mutex_unlock( &worker->head.lock ); + } + vlc_mutex_unlock( &worker->tail.lock ); + + if( item == NULL ) + break; + + if( worker->conf.pf_start( worker->owner, item->entity, &handle ) ) + { + worker->conf.pf_release( item->entity ); + free( item ); + continue; + } + + for( ;; ) + { + vlc_mutex_lock( &worker->head.lock ); + + bool const b_timeout = worker->head.deadline <= mdate(); + worker->head.probe_request = false; + + vlc_mutex_unlock( &worker->head.lock ); + + if( b_timeout || + worker->conf.pf_probe( worker->owner, handle ) ) + { + worker->conf.pf_stop( worker->owner, handle ); + worker->conf.pf_release( item->entity ); + free( item ); + break; + } + + vlc_mutex_lock( &worker->head.lock ); + if( worker->head.probe_request == false && + worker->head.deadline > mdate() ) + { + vlc_cond_timedwait( &worker->head.wait, &worker->head.lock, + worker->head.deadline ); + } + vlc_mutex_unlock( &worker->head.lock ); + } + } + + return NULL; +} + +static void BackgroundWorkerCancel( struct background_worker* worker, void* id) +{ + vlc_mutex_lock( &worker->tail.lock ); + for( size_t i = 0; i < vlc_array_count( &worker->tail.data ); ) + { + struct bg_queued_item* item = + vlc_array_item_at_index( &worker->tail.data, i ); + + if( id == NULL || item->id == id ) + { + vlc_array_remove( &worker->tail.data, i ); + worker->conf.pf_release( item->entity ); + free( item ); + continue; + } + + ++i; + } + vlc_mutex_unlock( &worker->tail.lock ); + + vlc_mutex_lock( &worker->head.lock ); + while( ( id == NULL && worker->head.active ) + || ( id != NULL && worker->head.id == id ) ) + { + worker->head.deadline = VLC_TS_0; + vlc_cond_broadcast( &worker->head.wait ); + vlc_cond_wait( &worker->head.wait, &worker->head.lock ); + } + vlc_mutex_unlock( &worker->head.lock ); +} + +struct background_worker* background_worker_New( void* owner, + struct background_worker_config* conf ) +{ + struct background_worker* worker = malloc( sizeof *worker ); + + if( unlikely( !worker ) ) + return NULL; + + worker->conf = *conf; + worker->owner = owner; + worker->head.id = NULL; + worker->head.active = false; + worker->head.deadline = VLC_TS_INVALID; + + vlc_mutex_init( &worker->head.lock ); + vlc_cond_init( &worker->head.wait ); + + vlc_array_init( &worker->tail.data ); + vlc_mutex_init( &worker->tail.lock ); + + return worker; +} + +int background_worker_Push( struct background_worker* worker, void* entity, + void* id, int timeout ) +{ + struct bg_queued_item* item = malloc( sizeof( *item ) ); + + if( unlikely( !item ) ) + return VLC_EGENERIC; + + item->id = id; + item->entity = entity; + item->timeout = timeout > 0 ? timeout : worker->conf.default_timeout; + + vlc_mutex_lock( &worker->tail.lock ); + vlc_array_append( &worker->tail.data, item ); + vlc_mutex_unlock( &worker->tail.lock ); + + vlc_mutex_lock( &worker->head.lock ); + if( worker->head.active == false ) + { + worker->head.probe_request = false; + worker->head.active = + !vlc_clone_detach( NULL, Thread, worker, VLC_THREAD_PRIORITY_LOW ); + } + + if( worker->head.active ) + worker->conf.pf_hold( item->entity ); + + int ret = worker->head.active ? VLC_SUCCESS : VLC_EGENERIC; + vlc_mutex_unlock( &worker->head.lock ); + + return ret; +} + +void background_worker_Cancel( struct background_worker* worker, void* id ) +{ + BackgroundWorkerCancel( worker, id ); +} + +void background_worker_RequestProbe( struct background_worker* worker ) +{ + vlc_mutex_lock( &worker->head.lock ); + worker->head.probe_request = true; + vlc_cond_broadcast( &worker->head.wait ); + vlc_mutex_unlock( &worker->head.lock ); +} + +void background_worker_Delete( struct background_worker* worker ) +{ + BackgroundWorkerCancel( worker, NULL ); + vlc_array_clear( &worker->tail.data ); + free( worker ); +} diff --git a/src/misc/background_worker.h b/src/misc/background_worker.h new file mode 100644 index 0000000..1382781 --- /dev/null +++ b/src/misc/background_worker.h @@ -0,0 +1,182 @@ +/***************************************************************************** + * Copyright (C) 2017 VLC authors and VideoLAN + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef BACKGROUND_WORKER_H__ +#define BACKGROUND_WORKER_H__ + +struct background_worker_config { + /** + * Default timeout for completing a task + * + * If less-than 0 a task can run indefinitely without being killed, whereas + * a positive value denotes the maximum number of milliseconds a task can + * run before \ref pf_stop is called to kill it. + **/ + mtime_t default_timeout; + + /** + * Release an entity + * + * This callback will be called in order to decrement the ref-count of a + * entity within the background-worker. It will happen either when \ref + * pf_stop has finished executing, or if the entity is removed from the + * queue (through \ref background_worker_Cancel) + * + * \param entity the entity to release + **/ + void( *pf_release )( void* entity ); + + /** + * Hold a queued item + * + * This callback will be called in order to increment the ref-count of an + * entity. It will happen when the entity is pushed into the queue of + * pending tasks as part of \ref background_worker_Push. + * + * \param entity the entity to hold + **/ + void( *pf_hold )( void* entity ); + + /** + * Start a new task + * + * This callback is called in order to construct a new background task. In + * order for the background-worker to be able to continue processing + * incoming requests, \ref pf_start is meant to start a task (such as a + * thread), and then store the associated handle in `*out`. + * + * The value of `*out` will then be the value of the argument named `handle` + * in terms of \ref pf_probe and \ref pf_stop. + * + * \param owner the owner of the background-worker + * \param entity the entity for which a task is to be created + * \param out [out] `*out` shall, on success, refer to the handle associated + * with the running task. + * \return VLC_SUCCESS if a task was created, an error-code on failure. + **/ + int( *pf_start )( void* owner, void* entity, void** out ); + + /** + * Probe a running task + * + * This callback is called in order to see whether or not a running task has + * finished or not. It can be called anytime between a successful call to + * \ref pf_start, and the corresponding call to \ref pf_stop. + * + * \param owner the owner of the background-worker + * \param handle the handle associated with the running task + * \return 0 if the task is still running, any other value if finished. + **/ + int( *pf_probe )( void* owner, void* handle ); + + /** + * Stop a running task + * + * This callback is called in order to stop a running task. If \ref pf_start + * has created a non-detached thread, \ref pf_stop is where you would + * interrupt and then join it. + * + * \warning This function is called either after \ref pf_probe has stated + * that the task has finished, or if the timeout (if any) for the + * task has been reached. + * + * \param owner the owner of the background-worker + * \parma handle the handle associated with the task to be stopped + **/ + void( *pf_stop )( void* owner, void* handle ); +}; + +/** + * Create a background-worker + * + * This function creates a new background-worker using the passed configuration. + * + * \warning all members of `config` shall have been set by the caller. + * \warning the returned resource must be destroyed using \ref + * background_worker_Delete on success. + * + * \param owner the owner of the background-worker + * \param config the background-worker's configuration + * \return a pointer-to the created background-worker on success, + * `NULL` on failure. + **/ +struct background_worker* background_worker_New( void* owner, + struct background_worker_config* config ); + +/** + * Request the background-worker to probe the current task + * + * This function is used to signal the background-worker that it should do + * another probe to see whether the current task is still alive. + * + * \warning Note that the function will not wait for the probing to finish, it + * will simply ask the background worker to recheck it as soon as + * possible. + * + * \param worker the background-worker + **/ +void background_worker_RequestProbe( struct background_worker* worker ); + +/** + * Push an entity into the background-worker + * + * This function is used to push an entity into the queue of pending work. The + * entities will be processed in the order in which they are received (in terms + * of the order of invocations in a single-threaded environment). + * + * \param worker the background-worker + * \param entity the entity which is to be queued + * \param id a value suitable for identifying the entity, or `NULL` + * \param timeout the timeout of the entity in milliseconds, if `0` the + * default-timeout of the background-worker will be used. + * \return VLC_SUCCESS if the entity was successfully queued, an error-code on + * failure. + **/ +int background_worker_Push( struct background_worker* worker, void* entity, + void* id, int timeout ); + +/** + * Remove entities from the background-worker + * + * This function is used to remove processing of a certain entity given its + * associated id, or to remove all queued (including currently running) + * entities. + * + * \warning if the `id` passed refers to an entity that is currently being + * processed, the call will block until the task has been terminated. + * + * \param worker the background-worker + * \param id NULL if every entity shall be removed, and the currently running + * task (if any) shall be cancelled. + **/ +void background_worker_Cancel( struct background_worker* worker, void* id ); + +/** + * Delete a background-worker + * + * This function will destroy a background-worker created through \ref + * background_worker_New. It will effectively stop the currently running task, + * if any, and empty the queue of pending entities. + * + * \warning If there is a currently running task, the function will block until + * it has been stopped. + * + * \param worker the background-worker + **/ +void background_worker_Delete( struct background_worker* worker ); +#endif _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
