playlist/background_worker: introduce background-worker utility
authorFilip Roséen <filip@atch.se>
Mon, 10 Oct 2016 23:36:25 +0000 (01:36 +0200)
committerHugo Beauzée-Luyssen <hugo@beauzee.fr>
Wed, 29 Mar 2017 15:35:29 +0000 (17:35 +0200)
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 <hugo@beauzee.fr>
src/Makefile.am
src/misc/background_worker.c [new file with mode: 0644]
src/misc/background_worker.h [new file with mode: 0644]

index 1392248..954322b 100644 (file)
@@ -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 (file)
index 0000000..8009a60
--- /dev/null
@@ -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 (file)
index 0000000..1382781
--- /dev/null
@@ -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