Hi.

Some time ago, I started working on the project to update libavformat's
protocols to run with a proper event loop, instead of the several half-assed
event loops we have here and there in the code.

I started by writing the documentation for the low-level API, making sure it
is an API that (1) is sufficient for the high-level API and (2) I can
deliver on Unix using poll() and pthreads and could deliver with GLib, libev
and probably libuv.

Please have a look at it. In particular, if you know system/event
programming for other operating systems, please have a look to tell me if
there are flaws in that regard.

Here are a few comments:

The structure of my projects are:

- AVWorker, single-threaded low-level API, with the possibility of having
  several implementations to accommodate different operating systems or
  integrate into existing frameworks.

- AVScheduler, multi-threaded high-level API, with the possibility of having
  several implementations to integrate into existing frameworks.

- Redesign AVIO so that it works as tasks in an AVScheduler.

- Redesign libavfilter so that activating filters is tasks in an
  AVScheduler.

Note that I have made implementation choices different from what we are used
to. In particular, when it comes to making the API future-proof. Adding
low-level tasks needs to be extremely lightweight, to the exclusion of
dynamic allocation: our old and t[ier]+d foo_alloc() / foo_free() / add
fields at the end does not cut it. If you want to criticize these choices,
please make sure you understand how they work, what constraints I am working
with and what benefit they bring, and preferably have an alternate proposal.

Here is the current state of worker.h. Now I will be working on scheduler.h.

Regards,

-- 
  Nicolas George
/*
 * This file is part of FFmpeg.
 *
 * FFmpeg 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.
 *
 * FFmpeg 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 FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef AVUTIL_WORKER_H
#define AVUTIL_WORKER_H

/**
 * @defgroup lavu_worker AVWorker
 *
 * AVWorker: single-threaded event loop.
 *
 * AVWorker is an API to implement single-threaded event loops.
 * It is the building block used to implement multi-threaded event loops,
 * see the AVScheduler API.
 *
 * You want to consider this API if:
 *
 * 1. You need a simple event loop and lightweight initialization is more
 *    important than the extra performance bought by parallelism.
 *
 * 2. You want to implement an AVWorker capable of taking advantage of the
 *    specifics of your OS or framework to use with AVScheduler.
 *
 * Otherwise, you should probably be using AVScheduler.
 *
 * In normal circumstances, an AVWorker has a set of tasks attached to it.
 * It will wait for one of these tasks to be ready and execute it.
 * This can be repeated in a loop until the end of operation is reached.
 *
 * Different types of tasks exist, depending on the condition for their
 * execution.
 * There are tasks to wait for a specific time.
 * There are tasks to wait for network activity or other IPC.
 * There are tasks to be executed as soon as computing time is available.
 *
 * Even though AVWorker is single-threaded, it can be thread-safe. This is
 * specified through a flag.
 * If an AVWorker is not thread-safe, applications must ensure to only ever
 * use it from a single thread; tasks can still do threads internally of
 * course.
 * If the AVWorker is thread-safe, it still must be run on a single thread,
 * but other threads can add and remove tasks. Other threads can also
 * interrupt a task prematurely, if it is implemented for that particular
 * task.
 *
 * Tasks have a priority. If several tasks are ready to run, the one with
 * the highest priority will be chosen. This API does not define the range
 * of priorities.
 *
 * Tasks have a duration, which is defined as a rough upper bound to the
 * time it will actually take to complete. Per convention, use 1000 for
 * tasks that only do a little processing and 10000 for tasks that may do a
 * few system calls or dynamic allocation. Applications can limit the time
 * an AVWorker will be running: tasks that would finish too late are not
 * considered. The value (unsigned)-1 = UINT_MAX means no limit.
 *
 * Tasks have a interrupt_latency parameter, which describes the time it
 * takes to interrupt it from another thread, in a way similar to its
 * duration. Applications can limit the latency, tasks with a higher latency
 * will not be considered. If a tasks cannot be interrupted, its latency
 * should be the same as its duration.
 *
 * Examples and use cases:
 *
 * AVWorker is designed to serve as the back-end for AVScheduler: an
 * AVScheduler will start as many threads as it should and run an AVWorker
 * in each.
 *
 * If a task waits on an external library with a timeout, its duration
 * should be set to the timeout plus a little margin.
 *
 * If a task is computationally intensive, it can become interruptible by
 * checking on an atomic flag every so often. The interrupt latency will be
 * an upper bound of the time between the checks.
 *
 * @{
 */

typedef struct AVWorker AVWorker;
typedef struct AVWorkerTask AVWorkerTask;
typedef struct AVWorkerCatalog AVWorkerCatalog;
typedef struct AVWorkerDescr AVWorkerDescr;
typedef enum AVWorkerTaskType AVWorkerTaskType;

/**
 * Context for a singe-threaded event loop.
 *
 * A thread running an AVWorker will execute tasks in reaction to system
 * events, external events, time, etc.
 *
 * The structure is mostly made of callbacks, to allow different
 * implementations to coexist. Applications can define their own AVWorker
 * and use them.
 *
 * If the AVWorker has the AV_WORKER_FLAG_THREAD_SAFE flag, then tasks can
 * be added, removed and updated from different threads. Otherwise, it
 * should only be handled from its own thread.
 */
struct AVWorker {

    /**
     * Size of the AVWorker structure itself.
     * It must be the size of the structure as defined here, but if later
     * versions add fields at the end, they will not access them, making
     * dynamic linking possible without changing the ABI.
     * XXX find a way to avoid diplicating this paragraph
     */
    size_t self_size;

    /**
     * Description of the implementation of this AVWorker.
     */
    const AVWorkerDescr *descr;

    /**
     * Implementation of av_worker_run_once().
     */
    int (*run_once)(AVWorker *worker, unsigned max_duration, unsigned max_latency);

    /**
     * Implementation of av_worker_task_add().
     */
    int (*task_add)(AVWorkerTask *task, AVWorker *worker, int thread_safe);

    /**
     * Implementation of av_worker_task_remove().
     */
    void (*task_remove)(AVWorkerTask *task, AVWorker *worker, int thread_safe);

    /**
     * Implementation of av_worker_task_update().
     */
    void (*task_update)(AVWorkerTask *task, AVWorker *worker, unsigned mask, int thread_safe);

    /**
     * Current time of the AVWorker according to the monotonic clock.
     */
    int64_t now_monotonic;

    /**
     * Current time of the AVWorker according to the wall clock.
     */
    int64_t now_wall;

    /**
     * Flags for the AVWorker.
     * See AV_WORKER_FLAG_*.
     */
    unsigned flags;

    /**
     * Return value for av_worker_run().
     * If set to a non-zero value from the running task, cause
     * av_worker_run() to return after its completion.
     * For a threa-safe versio, see av_worker_break().
     */
    int return_value;

};

/**
 * This AVWorker is thread-safe: tasks can be added, updated and removed
 * from other threads.
 */
#define AV_WORKER_FLAG_THREAD_SAFE              0x00000001

/**
 * A task for an AVWorker.
 *
 * Tasks are allocated by the application.
 * A task can be added to one AVWorker at a time.
 *
 * Note that this structure does not contain an opaque pointer free for the
 * application. The preferred way to relate a task to the data it needs to
 * run is to have the AVWorkerTask part of a structure, and use pointer
 * arithmetic to derive a pointer to the structure from the pointer to the
 * task.
 * XXX I will probably add a macro to do that cleanly.
 */
struct AVWorkerTask {

    /**
     * Size of the AVWorkerTask structure itself.
     * It must be the size of the structure as defined here, but if later
     * versions add fields at the end, they will not access them, making
     * dynamic linking possible without changing the ABI.
     */
    size_t self_size;

    /**
     * Callback to execute the task.
     */
    void (*execute)(AVWorkerTask *); // XXX what arguments do we need?

    /**
     * Callback to interrupt the task from another thread.
     */
    void (*interrupt)(AVWorkerTask *);

    /**
     * AVWorker this task is attached to.
     */
    AVWorker *worker;

    /**
     * Type of the task.
     * See enum AVWorkerTaskType.
     */
    AVWorkerTaskType type;

    /**
     * Priority of the task.
     * An AVWorker should always run one of the tasks with the hightest
     * prirority that can be run.
     */
    unsigned priority;

    /**
     * Rough upper bound to the duration of the task, in microseconds.
     * Tasks will not be run if there is a time-based task with a higher
     * priority scheduled in less than their duration.
     */
    unsigned duration;

    /**
     * Rough upper bound to the latency for interrupting the task, in
     * microseconds.
     */
    unsigned interrupt_latency;

    /**
     * Task flags, see AV_WORKER_TASK_FLAG_*.
     */
    unsigned flags;

    /**
     * Integer parameter.
     * For AV_WORKER_TASK_TYPE_TIME_MONOTONIC and
     * AV_WORKER_TASK_TYPE_TIME_WALL, the time in microseconds.
     * For AV_WORKER_TASK_TYPE_UNIX_FD, the file descriptor.
     */
    int64_t param;

    /**
     * Enable mask.
     * For all types, if 0 the task is disabled.
     * For AV_WORKER_TASK_TYPE_UNIX_FD, a combination of
     * AV_WORKER_TASK_POLLIN and AV_WORKER_TASK_POLLOUT.
     */
    unsigned enable_mask;

    /**
     * Enable state, similar to enable_mask, set by AVWorker before running
     * the task.
     */
    unsigned enable_state;

    /**
     * Reserved for use by the AVWorker.
     */
    union {
        void *p;
        int i;
        intmax_t imax;
        double d;
    } worker_reserved[8];

};

/**
 * Repeating task.
 * If this flag is set, the AVWorker will keep it after executing it.
 * Note that if it is a time-related task, its time will need to be updated.
 * If this flag is not set, the AVWorker will remove and disown the task
 * before executing it.
 */
#define AV_WORKER_TASK_FLAG_REPEAT              0x00000001

/**
 * Utility task.
 * If an AVWorker has only utility tasks attached, av_worker_run() will
 * return.
 */
#define AV_WORKER_TASK_FLAG_UTILITY             0x00000002

/**
 * The type of an AVWorkerTask
 */
enum AVWorkerTaskType {

    /**
     * Placeholder type. Must not be added to an AVWorker.
     */
    AV_WORKER_TASK_TYPE_NONE,

    /**
     * The task must be performed as soon as possible.
     */
    AV_WORKER_TASK_TYPE_IMMEDIATE,

    /**
     * The task must be performed when a certain time is reached on the
     * monotonic clock.
     */
    AV_WORKER_TASK_TYPE_TIME_MONOTONIC,

    /**
     * The task must be performed when a certain time is reached on the
     * wall clock.
     */
    AV_WORKER_TASK_TYPE_TIME_WALL,

    /**
     * The task must be performed when an Unix file descriptor becomes ready.
     */
    AV_WORKER_TASK_TYPE_UNIX_FD,

    /* XXX
     * I will implement AVWorkerUnix that supports these types, it will
     * work more on less on other operating systems, and we will be on par
     * with the current features. But somebody can decide to implement other
     * OS-specific workers, and add more OS-specific types here.
     */

    /**
     * Types starting from this value are free for applications.
     */
    AV_WORKER_TASK_TYPE_CUSTOM = 0x10000000,

};

/**
 * For AV_WORKER_TASK_TYPE_UNIX_FD, check for input.
 */
#define AV_WORKER_TASK_POLLIN  1

/**
 * For AV_WORKER_TASK_TYPE_UNIX_FD, check for output.
 */
#define AV_WORKER_TASK_POLLOUT 2

/**
 * Catalog of AVWorker implementations.
 *
 * It allows to create AVWorker of different types, with feature
 * compromises.
 *
 * For example, with an AVWorkerCatalog, an application or library can
 * request a thread-safe AVWorker that supports Unix file descriptors.
 *
 * XXX do we want to be able to dynamically add?
 */
struct AVWorkerCatalog {

    /**
     * Size of the AVWorker structure itself.
     * It must be the size of the structure as defined here, but if later
     * versions add fields at the end, they will not access them, making
     * dynamic linking possible without changing the ABI.
     */
    size_t self_size;

    /**
     * Get the first AVWorkerDescr in a linked list.
     */
    const AVWorkerDescr *(*first)(void);

};

/**
 * Description of an AVWorker implementation.
 *
 * With this structure, it is possible to allocate and free AVWorker with a
 * specific implementation or with requested features.
 *
 * Most of the fields of this structure describe the features that will
 * always be available on an AVWorker created by this description.
 */
struct AVWorkerDescr {

    /**
     * Size of the AVWorkerTask structure itself.
     * It must be the size of the structure as defined here, but if later
     * versions add fields at the end, they will not access them, making
     * dynamic linking possible without changing the ABI.
     */
    size_t self_size;

    /**
     * Get the AVWorkerDescr in the linked list.
     * XXX I use a function pointer rather than a pointer to let
     * applications use av_worker_get_builtin at the tail of their linked
     * list, to provide extra implementations. av_worker_get_builtin can be
     * a static initializer, but av_worker_get_builtin() cannot.
     */
    const AVWorkerDescr *(*next)(void);

    /**
     * Callback to create a worker of this type.
     * @return  >= 0 on success or an AVERROR code.
     */
    int (*create)(AVWorker **, unsigned flags);

    /**
     * Callback to free a worker of this type.
     */
    void (*freep)(AVWorker **);

    /**
     * List of supported AVWorkerTaskType.
     * Terminated by AV_WORKER_TASK_TYPE_NONE. XXX or a specific struct with a count?
     */
    const AVWorkerTaskType *supported_types;

    /**
     * Flags that will always be enabled.
     */
    unsigned flags_min;

    /* XXX do we need flags_max? */

};

/**
 * Return the AVWorkerCatalog for all AVWorker implementations that were
 * built into libavutil.
 */
const AVWorkerCatalog *av_worker_get_builtin(void);

/**
 * Find an AVWorkerDescr in an AVWorkerCatalog with specific features.
 */
const AVWorkerDescr *av_worker_get_with_features(const AVWorkerCatalog *catalog,
                                                 const AVWorkerTaskType *required_types,
                                                 unsigned required_flags);

/**
 * Create an AVWorker from a description.
 */
int av_worker_new(AVWorker **rworker, const AVWorkerCatalog *catalog);

/**
 * Free an AVWorker.
 * Must not be used with a custom implementation that does not have a
 * description.
 */
void av_worker_freep(AVWorker **rworker);

/**
 * Create an AVWorker from an AVWorkerCatalog with specific features.
 */
int av_worker_new_with_features(AVWorker **rworker,
                                const AVWorkerCatalog *catalog,
                                const AVWorkerTaskType *required_types,
                                unsigned required_flags);

/**
 * Run an AVWorker and execute at most one attached task.
 *
 * Tasks with latency greater than max_latency will not run, nor tasks that
 * would exceed max_duration when adding the initial delay.
 *
 * @return  AVERROR_EOF if there are no more tasks,
 *          an AVERROR code if a system error happens,
 *          0 if a task was executed,
 *          or any value given to av_worker_break().
 */
int av_worker_run_once(AVWorker *worker, unsigned max_duration, unsigned max_latency);

/**
 * Set no limit on the duration.
 */
#define AV_WORKER_DURATION_UNLIMITED ((unsigned)-1)

/**
 * Run an AVWorker and execute the attached tasks.
 *
 * Tasks with latency greater than max_latency will not run.
 * To set a time limit on the running, add a time-based task.
 * TODO provide one all ready with a macro
 *
 * @return  AVERROR_EOF if there are no more tasks,
 *          an AVERROR code if a system error happens,
 *          or any value given to av_worker_break().
 */
int av_worker_run(AVWorker *worker, unsigned max_latency);

/**
 * Cause an AVWorker to stop running and av_worker_run() to return ret.
 * This function must only be called on a thread-safe AVWorker.
 * If a task is currently being executed and its remaining duration is
 * greater than latency, try to interrupt it prematurely;
 * use -1 to avoid.
 */
void av_worker_break(AVWorker *worker, int ret, unsigned latency);

/**
 * Add a task to the AVWorker.
 * Set thread_safe to 0 if the AVWorker is not running or if calling from
 * the executed task.
 * @return  >= 0 for success or an AVERROR code.
 */
int av_worker_task_add(AVWorkerTask *task, AVWorker *worker, int thread_safe);

/**
 * Remove a task from its AVWorker.
 * Set thread_safe to 0 if the AVWorker is not running or if calling from
 * the executed task.
 * After this function returns, the AVWorker code will no longer access the
 * AVWorkerTask structure.
 * Note that an AVWorkerTask that was removed cannot be interrupted.
 */
void av_worker_task_remove(AVWorkerTask *task, int thread_safe);

/**
 * Update a task in its worker after changing some of its parameters.
 * It should be faster than removing and re-adding it.
 * The type of task cannot be changed.
 * The mask is a combination of AV_WORKER_TASK_UPDATE_* to indicate which
 * parameter actually changed.
 * Set thread_safe to 0 if the AVWorker is not running or if calling from
 * the executed task.
 * @return  >= 0 for success or an AVERROR code.
 */
int av_worker_task_update(AVWorkerTask *task, unsigned mask, int thread_safe);

/**
 * Update the enable mask of the task.
 */
#define AV_WORKER_TASK_UPDATE_ENABLE            0x00000001

/**
 * Update the integer parameter (time or file descriptor).
 */
#define AV_WORKER_TASK_UPDATE_PARAM             0x00000002

/**
 * @}
 */

#endif /* AVUTIL_WORKER_H */

Attachment: signature.asc
Description: PGP signature

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".

Reply via email to