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 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".