Commit 537fd3b0 authored by Filip Roséen's avatar Filip Roséen Committed by 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: default avatarHugo Beauzée-Luyssen <hugo@beauzee.fr>
parent 1e49d593
......@@ -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 \
......
/*****************************************************************************
* 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 );
}
/*****************************************************************************
* 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment