[vlc-devel] [RFC 1/2] executor: introduce new executor API

Steve Lhomme robux4 at ycbcr.xyz
Thu Aug 27 07:09:00 CEST 2020


On 2020-08-26 19:19, Romain Vimont wrote:
> Introduce a new API to execute "runnables". The execution can be
> automatically interrupted on timeout or via an explicit cancelation
> request.
> ---
>   include/vlc_executor.h | 194 +++++++++++++++
>   src/Makefile.am        |   1 +
>   src/libvlccore.sym     |   4 +
>   src/misc/executor.c    | 545 +++++++++++++++++++++++++++++++++++++++++
>   4 files changed, 744 insertions(+)
>   create mode 100644 include/vlc_executor.h
>   create mode 100644 src/misc/executor.c
> 
> diff --git a/include/vlc_executor.h b/include/vlc_executor.h
> new file mode 100644
> index 0000000000..4dd8b93628
> --- /dev/null
> +++ b/include/vlc_executor.h
> @@ -0,0 +1,194 @@
> +/*****************************************************************************
> + * vlc_executor.h
> + *****************************************************************************
> + * Copyright (C) 2020 Videolabs, 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 VLC_EXECUTOR_H
> +#define VLC_EXECUTOR_H
> +
> +#include <vlc_common.h>
> +#include <vlc_tick.h>
> +
> +# ifdef __cplusplus
> +extern "C" {
> +# endif
> +
> +/** Executor type (opaque) */
> +typedef struct vlc_executor vlc_executor_t;
> +
> +/**
> + * A Runnable is intended to be run from another thread by an executor.
> + */
> +struct vlc_runnable {
> +
> +    /**
> +     * This function is to be executed by a vlc_executor_t.
> +     *
> +     * It must implement the actions (arbitrarily long) to execute from an
> +     * executor thread, synchronously. As soon as run() returns, the execution
> +     * of this runnable is complete.
> +     *
> +     * After the runnable is submitted to an executor via
> +     * vlc_executor_Submit(), the run() function is executed at most once (zero
> +     * if the execution is canceled before it was started).
> +     *
> +     * It may not be NULL.
> +     *
> +     * \param userdata the userdata provided to vlc_executor_Submit()
> +     */
> +    void

Why

> +    (*run)(void *userdata);

Do you
use two lines
for this ?

When grepping for code I always stumble such unreadable lines. I have to 
open the files for each of them to read the actual prototype.

> +
> +    /**
> +     * This function attempts to interrupt the execution of run().
> +     *
> +     * If not NULL, it may be called on vlc_executor_Cancel() or on timeout.
> +     *
> +     * It is called from a thread different from the one executing run(). It is
> +     * free to do any actions to "interrupt" the execution of run() (set a
> +     * flag, close a file descriptor, etc.).
> +     *
> +     * The runnable will be considered "finished" once run() actually
> +     * terminates.
> +     *
> +     * It should be quick, not to block the interruption of other runnables.
> +     *
> +     * \param userdata the userdata provided to vlc_executor_Submit()
> +     */
> +    void
> +    (*interrupt)(void *userdata);
> +
> +    /**
> +     * This function notifies the end of the execution.
> +     *
> +     * If not NULL, it is called either:
> +     *  - when run() terminates;
> +     *  - when the task is canceled before run() is called.
> +     *
> +     * In other words, it is always called in the end if vlc_executor_Submit()
> +     * returns VLC_SUCCESS.
> +     *
> +     * \param userdata the userdata provided to vlc_executor_Submit()
> +     */
> +    void
> +    (*on_finished)(void *userdata);
> +};
> +
> +/**
> + * Create a new executor.
> + *
> + * \param parent      a VLC object
> + * \param max_threads the maximum number of threads used to execute runnables
> + * \return a pointer to a new executor, or NULL if an error occurred
> + */
> +VLC_API vlc_executor_t *
> +vlc_executor_New(vlc_object_t *parent, unsigned max_threads);
> +
> +/**
> + * Delete an executor.
> + *
> + * Cancels all the pending an running task (interrupting them if necessary),
> + * waits for all the thread to complete, and delete the executor instance.
> + *
> + * \param executor the executor
> + */
> +VLC_API void
> +vlc_executor_Delete(vlc_executor_t *executor);
> +
> +/**
> + * Submit a runnable for execution.
> + *
> + * The struct vlc_runnable is not copied, it must exist until the end of the
> + * execution (the user is expected to pass a pointer to a static const
> + * structure).
> + *
> + * An id may optionally be provided in order to explicitly cancel the task
> + * later with vlc_executor_Cancel().
> + *
> + * A timeout may optionally be provided to interrupt the execution after some
> + * delay. The way the interruption is actually handled must be provided by the
> + * user via the runnable callback "interrupt". It is an error to provide a
> + * timeout without an interrupt callback.
> + *
> + * Here is a simple example (without interruption):
> + *
> + * \code{c}
> + *  static void Run(void *userdata)
> + *  {
> + *      char *str = userdata;
> + *      printf("start of %s\n", str);
> + *      sleep(3);
> + *      printf("end of %s\n", str);
> + *  }
> + *
> + *  static void OnFinished(void *userdata)
> + *  {
> + *      free(userdata);
> + *  }
> + *
> + *  static const struct vlc_runnable runnable = {
> + *      .run = Run,
> + *      .on_finished = OnFinished,
> + *  };
> + *
> + *  void foo(vlc_executor_t *executor, const char *name)
> + *  {
> + *      // error handling replaced by assertions for brevity
> + *      char *str = strdup(name);
> + *      assert(str);
> + *      int ret = vlc_executor_Submit(executor, &runnable, str, NULL, 0);
> + *      assert(ret == VLC_SUCCESS);
> + *  }
> + * \endcode
> + *
> + * \param executor the executor
> + * \param runnable the task to run
> + * \param userdata the userdata to pass to all runnable callbacks
> + * \param id       a user-provided id to associate to the task, used to cancel
> + *                 the execution (may be NULL)
> + * \param timeout  the delay before the task is automatically interrupted
> + *                 (0 for no timeout, negative values are illegal)
> + * \return VLC_SUCCESS on success, another value on error
> + */
> +VLC_API int
> +vlc_executor_Submit(vlc_executor_t *executor,
> +                    const struct vlc_runnable *runnable, void *userdata,
> +                    void *id, vlc_tick_t timeout);
> +
> +/**
> + * Cancel all submitted tasks having the specified id.
> + *
> + * If id is NULL, then cancel all tasks.
> + *
> + * If may_interrupt_if_running is true, then the current running tasks may be
> + * interrupted using the interrupt() callback.
> + *
> + * \param executor the executor
> + * \param id       the id of the tasks to cancel (NULL for all tasks)
> + * \param may_interrupt_if_running indicate if the tasks currently running may
> + *                                 be interrupted
> + */
> +VLC_API void
> +vlc_executor_Cancel(vlc_executor_t *executor, void *id,
> +                    bool may_interrupt_if_running);
> +
> +# ifdef __cplusplus
> +}
> +# endif
> +
> + #endif
> diff --git a/src/Makefile.am b/src/Makefile.am
> index 9e7c2931d2..456df7a6b2 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -350,6 +350,7 @@ libvlccore_la_SOURCES = \
>   	misc/actions.c \
>   	misc/background_worker.c \
>   	misc/background_worker.h \
> +	misc/executor.c \
>   	misc/md5.c \
>   	misc/probe.c \
>   	misc/rand.c \
> diff --git a/src/libvlccore.sym b/src/libvlccore.sym
> index ed43c17715..5ab0eec6ed 100644
> --- a/src/libvlccore.sym
> +++ b/src/libvlccore.sym
> @@ -958,3 +958,7 @@ vlc_video_context_GetType
>   vlc_video_context_GetPrivate
>   vlc_video_context_Hold
>   vlc_video_context_HoldDevice
> +vlc_executor_New
> +vlc_executor_Delete
> +vlc_executor_Execute
> +vlc_executor_Cancel
> diff --git a/src/misc/executor.c b/src/misc/executor.c
> new file mode 100644
> index 0000000000..88c7064b72
> --- /dev/null
> +++ b/src/misc/executor.c
> @@ -0,0 +1,545 @@
> +/*****************************************************************************
> + * misc/executor.c
> + *****************************************************************************
> + * Copyright (C) 2020 Videolabs, 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_executor.h>
> +
> +#include <vlc_atomic.h>
> +#include <vlc_list.h>
> +#include <vlc_threads.h>
> +#include "libvlc.h"
> +
> +/**
> + * An executor task is created on every call to vlc_executor_Submit().
> + *
> + * It contains the user-provided data (runnable, userdata, timeout) and the
> + * current execution state.
> + */
> +struct vlc_executor_task {
> +
> +    /** Node of vlc_executor.queue list */
> +    struct vlc_list node;
> +
> +    /** Task id, may be NULL (provided by the user) */
> +    void *id;
> +
> +    /** Timeout, 0 for no timeout, negative is illegal (provided by the user) */
> +    vlc_tick_t timeout;
> +
> +    /**
> +     * The task is "finished" if either:
> +     *   - the run() callback has completed, or
> +     *   - the run() callback has not been called and the task is canceled.
> +     */
> +    bool finished;
> +
> +    /**
> +     * Set to true if the task has been canceled.
> +     *
> +     * It is possible that the task is canceled but not finished ("cancel" has
> +     * been requested while run() was running, but run() is not completed yet).
> +     */
> +    bool canceled;
> +
> +    /** Date when run() is called, VLC_TICK_INVALID if not started. */
> +    vlc_tick_t start_date;
> +
> +    /** The associated runnable */
> +    const struct vlc_runnable *runnable;
> +    /** The user data passed to all runnable callbacks */
> +    void *userdata;
> +
> +    /** Refcount */
> +    vlc_atomic_rc_t rc;
> +};
> +
> +/**
> + * An executor can spawn several threads.
> + *
> + * This structure contains the data specific to one thread.
> + */
> +struct vlc_executor_thread {
> +    /** Node of vlc_executor.threads list */
> +    struct vlc_list node;
> +
> +    /** The executor owning the thread */
> +    vlc_executor_t *owner;
> +
> +    /** The current task executed by the thread, NULL if none */
> +    struct vlc_executor_task *current_task;
> +};
> +
> +/**
> + * The executor (also vlc_executor_t, exposed as opaque type in the public
> + * header).
> + */
> +struct vlc_executor {
> +    vlc_object_t obj;
> +    vlc_mutex_t lock;
> +
> +    /** Maximum number of threads to run the tasks */
> +    unsigned max_threads;
> +
> +    /** List of active vlc_executor_thread instances */
> +    struct vlc_list threads;
> +
> +    /** Thread count (in a separate field to quickly compare to max_threads) */
> +    unsigned nthreads;
> +
> +    /* Number of tasks requested but not finished (neither completed or
> +     * canceled) */
> +    unsigned unfinished;
> +
> +    /** Queue of vlc_executor_task_t */
> +    struct vlc_list queue;
> +
> +    /** Wait for nthreads == 0 (used for executor deletion) */
> +    vlc_cond_t nothreads_wait;
> +
> +    /** Wait for the queue to be non-empty */
> +    vlc_cond_t queue_wait;
> +
> +    /** Wait for the next task interruption deadline */
> +    vlc_cond_t interrupt_wait;
> +
> +    /** Indicate is the interrupt thread is running (to know if we need to
> +     * spawn one) */
> +    bool interrupt_thread_running;
> +
> +    /** True if executor deletion is requested */
> +    bool closing;
> +};
> +
> +vlc_executor_t *
> +vlc_executor_New(vlc_object_t *parent, unsigned max_threads)
> +{
> +    assert(max_threads);
> +    vlc_executor_t *executor =
> +        vlc_custom_create(parent, sizeof(*executor), "executor");
> +    if (!executor)
> +        return NULL;
> +
> +    vlc_mutex_init(&executor->lock);
> +
> +    executor->max_threads = max_threads;
> +    executor->nthreads = 0;
> +    executor->unfinished = 0;
> +
> +    vlc_list_init(&executor->threads);
> +    vlc_list_init(&executor->queue);
> +
> +    vlc_cond_init(&executor->nothreads_wait);
> +    vlc_cond_init(&executor->queue_wait);
> +    vlc_cond_init(&executor->interrupt_wait);
> +
> +    executor->closing = false;
> +
> +    return executor;
> +}
> +
> +static struct vlc_executor_task *
> +TaskNew(const struct vlc_runnable *runnable, void *userdata, void *id,
> +        vlc_tick_t timeout)
> +{
> +    struct vlc_executor_task *task = malloc(sizeof(*task));
> +    if (!task)
> +        return NULL;
> +
> +    task->id = id;
> +    task->timeout = timeout;
> +    task->finished = false;
> +    task->canceled = false;
> +    task->start_date = VLC_TICK_INVALID;
> +
> +    task->runnable = runnable;
> +    task->userdata = userdata;
> +
> +    vlc_atomic_rc_init(&task->rc);
> +    return task;
> +}
> +
> +static void
> +TaskHold(struct vlc_executor_task *task)
> +{
> +    vlc_atomic_rc_inc(&task->rc);
> +}
> +
> +static void
> +TaskRelease(struct vlc_executor_task *task)
> +{
> +    if (vlc_atomic_rc_dec(&task->rc))
> +        free(task);
> +}
> +
> +static void
> +TerminateTask(vlc_executor_t *executor, struct vlc_executor_task *task)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +
> +    assert(!task->finished);
> +    task->finished = true;
> +
> +    if (task->runnable->on_finished)
> +        task->runnable->on_finished(task->userdata);
> +
> +    assert(executor->unfinished > 0);
> +    --executor->unfinished;
> +
> +    TaskRelease(task);
> +}
> +
> +static struct vlc_executor_task *
> +FindFirstTaskToInterrupt(vlc_executor_t *executor)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +
> +    /* XXX To improve execution complexity, we could keep the interruptible
> +     * tasks in a priority queue. But there are typically only few threads, and
> +     * this is not called more than once per task, so keep things simple, even
> +     * if it's not optimal. */
> +
> +    struct vlc_executor_task *first = NULL;
> +    vlc_tick_t first_deadline = VLC_TICK_INVALID;
> +
> +    struct vlc_executor_thread *thread;
> +    vlc_list_foreach(thread, &executor->threads, node)
> +    {
> +        struct vlc_executor_task *task = thread->current_task;
> +        /* Only consider tasks with a timeout */
> +        if (task && task->timeout)
> +        {
> +            /* If there is a current task, then it is necessarily started */
> +            assert(task->start_date != VLC_TICK_INVALID);
> +
> +            vlc_tick_t deadline = task->start_date + task->timeout;
> +            if (!first || deadline < first_deadline)
> +            {
> +                first = task;
> +                first_deadline = deadline;
> +            }
> +        }
> +    }
> +
> +    return first;
> +}
> +
> +static void *
> +InterruptThreadRun(void *userdata)
> +{
> +    vlc_executor_t *executor = userdata;
> +
> +    vlc_mutex_lock(&executor->lock);
> +    for (;;)
> +    {
> +        struct vlc_executor_task *task = FindFirstTaskToInterrupt(executor);
> +        if (!task)
> +            /* There is no task to interrupt, terminate the interrupt thread */
> +            break;
> +
> +        assert(task->start_date != VLC_TICK_INVALID);
> +        assert(task->timeout);
> +        vlc_tick_t deadline = task->start_date + task->timeout;
> +
> +        /* The task may be released during the timedwait */
> +        TaskHold(task);
> +
> +        bool timed_out = vlc_cond_timedwait(&executor->interrupt_wait,
> +                                            &executor->lock, deadline) != 0;
> +
> +        if (timed_out && !task->finished && !task->canceled)
> +        {
> +            task->canceled = true;
> +            /* This request the task to stop itself. The interrupt() itself
> +             * should be immediate, but the task could terminate later (once is
> +             * run() callback is completed). */
> +            task->runnable->interrupt(task->userdata);
> +        }
> +
> +        TaskRelease(task);
> +    }
> +
> +    executor->interrupt_thread_running = false;
> +    vlc_mutex_unlock(&executor->lock);
> +
> +    return NULL;
> +}
> +
> +/**
> + * Make sure that an interrupt thread is running and will consider the
> + * interruption for a new task just added.
> + */
> +static int
> +RequireInterruptThread(vlc_executor_t *executor)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +
> +    if (executor->interrupt_thread_running)
> +        vlc_cond_signal(&executor->interrupt_wait);
> +    else
> +    {
> +        if (vlc_clone_detach(NULL, InterruptThreadRun, executor,
> +                             VLC_THREAD_PRIORITY_LOW))
> +        {
> +            vlc_mutex_unlock(&executor->lock);
> +            return VLC_EGENERIC;
> +        }
> +
> +        executor->interrupt_thread_running = true;
> +    }
> +
> +    return VLC_SUCCESS;
> +}
> +
> +static void
> +QueuePush(vlc_executor_t *executor, struct vlc_executor_task *task)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +
> +    vlc_list_append(&task->node, &executor->queue);
> +    vlc_cond_signal(&executor->queue_wait);
> +}
> +
> +static struct vlc_executor_task *
> +QueueTake(vlc_executor_t *executor, vlc_tick_t timeout)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +
> +    vlc_tick_t deadline = vlc_tick_now() + timeout;
> +    bool timed_out = false;
> +    while (!timed_out && !executor->closing
> +            && vlc_list_is_empty(&executor->queue))
> +        timed_out = vlc_cond_timedwait(&executor->queue_wait,
> +                                       &executor->lock, deadline) != 0;
> +
> +    if (executor->closing || timed_out)
> +        return NULL;
> +
> +    struct vlc_executor_task *task =
> +        vlc_list_first_entry_or_null(&executor->queue, struct vlc_executor_task,
> +                                     node);
> +    assert(task);
> +    vlc_list_remove(&task->node);
> +
> +    return task;
> +}
> +
> +static void
> +QueueRemoveAll(vlc_executor_t *executor, void *id)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +
> +    struct vlc_executor_task *task;
> +    vlc_list_foreach(task, &executor->queue, node)
> +    {
> +        if (!id || task->id == id)
> +        {
> +            task->canceled = true;
> +            vlc_list_remove(&task->node);
> +            TerminateTask(executor, task);
> +        }
> +    }
> +}
> +
> +static struct vlc_executor_thread *
> +ExecutorThreadNew(vlc_executor_t *executor)
> +{
> +    struct vlc_executor_thread *thread = malloc(sizeof(*thread));
> +    if (!thread)
> +        return NULL;
> +
> +    thread->owner = executor;
> +    thread->current_task = NULL;
> +    return thread;
> +}
> +
> +static void
> +ExecutorThreadDelete(struct vlc_executor_thread *thread)
> +{
> +    free(thread);
> +}
> +
> +static void
> +RemoveThread(struct vlc_executor_thread *thread)
> +{
> +    vlc_executor_t *executor = thread->owner;
> +
> +    vlc_mutex_lock(&executor->lock);
> +
> +    vlc_list_remove(&thread->node);
> +    assert(executor->nthreads > 0);
> +    --executor->nthreads;
> +    bool nothreads = !executor->nthreads;
> +    vlc_mutex_unlock(&executor->lock);
> +
> +    if (nothreads)
> +        vlc_cond_signal(&executor->nothreads_wait);
> +
> +    /* After vlc_cond_signal(), the executor instance may be deleted by
> +     * vlc_executor_Delete() */
> +
> +    ExecutorThreadDelete(thread);
> +}
> +
> +static void *
> +ThreadRun(void *userdata)
> +{
> +    struct vlc_executor_thread *thread = userdata;
> +    vlc_executor_t *executor = thread->owner;
> +
> +    vlc_mutex_lock(&executor->lock);
> +
> +    for (;;)
> +    {
> +        struct vlc_executor_task *task =
> +            QueueTake(executor, VLC_TICK_FROM_SEC(5));
> +        if (!task)
> +            /* Terminate this thread */
> +            break;
> +
> +        thread->current_task = task;
> +        task->start_date = vlc_tick_now();
> +        if (task->timeout)
> +            RequireInterruptThread(executor);
> +        vlc_mutex_unlock(&executor->lock);
> +
> +        /* Execute the user-provided runnable, without the executor lock */
> +        task->runnable->run(task->userdata);
> +
> +        vlc_mutex_lock(&executor->lock);
> +        thread->current_task = NULL;
> +        TerminateTask(executor, task);
> +    }
> +
> +    vlc_mutex_unlock(&executor->lock);
> +
> +    RemoveThread(thread);
> +    return NULL;
> +}
> +
> +static int
> +SpawnThread(vlc_executor_t *executor)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +    assert(executor->nthreads < executor->max_threads);
> +
> +    struct vlc_executor_thread *thread = ExecutorThreadNew(executor);
> +    if (!thread)
> +        return VLC_ENOMEM;
> +
> +    if (vlc_clone_detach(NULL, ThreadRun, thread, VLC_THREAD_PRIORITY_LOW))
> +    {
> +        ExecutorThreadDelete(thread);
> +        return VLC_EGENERIC;
> +    }
> +
> +    executor->nthreads++;
> +    vlc_list_append(&thread->node, &executor->threads);
> +
> +    return VLC_SUCCESS;
> +}
> +
> +int
> +vlc_executor_Submit(vlc_executor_t *executor,
> +                    const struct vlc_runnable *runnable, void *userdata,
> +                    void *id, vlc_tick_t timeout)
> +{
> +    assert(timeout >= 0);
> +    /* An interrupt callback must be provided if a timeout is requested.
> +     * Note that an interrupt callback may also be provided without timeout,
> +     * for explicit calls to vlc_executor_Cancel(). */
> +    assert(timeout == 0 || runnable->interrupt);
> +    assert(runnable->run);
> +
> +    struct vlc_executor_task *task = TaskNew(runnable, userdata, id, timeout);
> +    if (!task)
> +        return VLC_ENOMEM;
> +
> +    vlc_mutex_lock(&executor->lock);
> +    QueuePush(executor, task);
> +    if (++executor->unfinished > executor->nthreads
> +            && executor->nthreads < executor->max_threads)
> +        SpawnThread(executor);
> +    vlc_mutex_unlock(&executor->lock);
> +
> +    return VLC_SUCCESS;
> +}
> +
> +static void
> +CancelLocked(vlc_executor_t *executor, void *id, bool may_interrupt_if_running)
> +{
> +    vlc_mutex_assert(&executor->lock);
> +
> +    QueueRemoveAll(executor, id);
> +
> +    struct vlc_executor_thread *thread;
> +    vlc_list_foreach(thread, &executor->threads, node)
> +    {
> +        struct vlc_executor_task *task = thread->current_task;
> +        if (task && (!id || task->id == id) && !task->canceled)
> +        {
> +            /* If the task was not started or finished, it would not be stored
> +             * as a current task for the thread */
> +            assert(task->start_date != VLC_TICK_INVALID);
> +            assert(!task->finished);
> +
> +            if (may_interrupt_if_running && task->runnable->interrupt)
> +            {
> +                /* Mark the task as canceled only if we can interrupt it,
> +                 * otherwise it will complete normally. */
> +                task->canceled = true;
> +                task->runnable->interrupt(task->userdata);
> +            }
> +        }
> +    }
> +}
> +
> +void
> +vlc_executor_Cancel(vlc_executor_t *executor, void *id,
> +                    bool may_interrupt_if_running)
> +{
> +    vlc_mutex_lock(&executor->lock);
> +    CancelLocked(executor, id, may_interrupt_if_running);
> +    vlc_mutex_unlock(&executor->lock);
> +}
> +
> +void
> +vlc_executor_Delete(vlc_executor_t *executor)
> +{
> +    vlc_mutex_lock(&executor->lock);
> +
> +    executor->closing = true;
> +
> +    /* Cancel all tasks */
> +    CancelLocked(executor, NULL, true);
> +
> +    /* "closing" is now true, this will wake up any QueueTake() */
> +    vlc_cond_broadcast(&executor->queue_wait);
> +
> +    /* Wait until all threads are terminated */
> +    while (executor->nthreads)
> +        vlc_cond_wait(&executor->nothreads_wait, &executor->lock);
> +
> +    vlc_mutex_unlock(&executor->lock);
> +
> +    vlc_object_delete(executor);
> +}
> -- 
> 2.28.0
> 
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
> 


More information about the vlc-devel mailing list