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 */
signature.asc
Description: PGP signature
_______________________________________________ ffmpeg-devel mailing list [email protected] https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email [email protected] with subject "unsubscribe".
