Threading and Concurrency Model
Relevant source files
The following files were used as context for generating this wiki page:
The FluentDo Agent employs a sophisticated, high-performance concurrency model based on a combination of native OS threads and lightweight coroutines. This asynchronous, event-driven architecture is designed to handle high volumes of data with minimal resource overhead, ensuring non-blocking operations for both data collection and flushing. The core of this model is a central scheduler that manages timed events and orchestrates the execution of concurrent tasks.
This design is fundamental to achieving the project's goals of being a performant, "smaller footprint" agent suitable for production deployments.
Sources: source/README.md, source/src/flb_scheduler.c, source/src/flb_coro.c
Core Concepts
The concurrency model is built upon two primary abstractions: coroutines for cooperative multitasking and a scheduler for managing timed events.
Coroutines (flb_coro)
Coroutines are lightweight, stackful "threads" that are managed by the application rather than the operating system. They are the foundation for concurrent task execution within the agent, used extensively by input and output plugins. By using coroutines, the agent can manage thousands of concurrent operations without the high memory and scheduling overhead associated with native OS threads.
The lifecycle of a coroutine is managed through a simple set of API calls:
flb_coro_create(): Initializes a new coroutine.flb_coro_resume(): Starts or continues the execution of a coroutine.flb_coro_yield(): Pauses the currently running coroutine, transferring control back to the caller (often the scheduler or main event loop).flb_coro_destroy(): Frees the resources associated with a coroutine.
Sources: source/src/flb_coro.c:115-171
The following diagram illustrates the lifecycle of a coroutine.
Sources: source/src/flb_coro.c
The Scheduler (flb_scheduler)
The scheduler is the heart of the event loop. It is responsible for managing all time-based events in the system, such as collector intervals for input plugins or timed retries for output plugins. It maintains a list of timers in a binary heap, which allows for highly efficient retrieval of the next event to fire.
The main functions of the scheduler are:
flb_scheduler_create(): Initializes the scheduler context.flb_scheduler_timer_create(): Creates a new timer and adds it to the scheduler's heap.flb_scheduler_get_timeout(): Calculates the time in milliseconds until the next timer is due. This value is used to determine the timeout for the main event loop.flb_scheduler_run(): Invoked after the event loop wakes up. It checks for and executes the callbacks of any expired timers.
Sources: source/src/flb_scheduler.c:237-248, 290-321, 323-339
This sequence diagram shows how a plugin interacts with the scheduler to execute a timed callback.
Sources: source/src/flb_scheduler.c
Plugin Threading Model
Input and output plugins are the primary users of the concurrency framework. They operate in distinct contexts to ensure that data collection and data flushing do not block each other.
Input Plugin Threads
Each input plugin instance that acts as a "collector" runs within its own coroutine. This allows multiple input sources to be polled concurrently without needing a dedicated OS thread for each one.
The process is managed by flb_input_thread.c:
flb_input_thread_init()creates a coroutine for the plugin's collection logic.- The coroutine's entry point is the
flb_input_thread_collect()function. - This function enters an infinite loop where it:
- Calls the plugin's specific
cb_collectfunction to gather data. - Pauses execution for the configured
intervalby creating a scheduler timer and yielding the coroutine. - Is resumed by the scheduler once the timer expires.
- Calls the plugin's specific
Sources: source/src/flb_input_thread.c:38-100
The data collection flow for an input plugin is as follows:
Sources: source/src/flb_input_thread.c:53-98
Output Plugin Threads
Output plugins operate in dedicated OS threads, allowing them to perform blocking I/O operations (like sending data over the network) without stalling the entire agent. Each output plugin can have one or more worker threads.
The lifecycle of an output thread is managed by flb_output_thread.c:
- The thread's main entry point is
flb_output_thread_func(). - The thread waits on a semaphore (
sem_pending_tasks) until a flush task is ready. - When woken, it retrieves a task and invokes
flb_output_flush()to process and send a chunk of data. - If the flush fails and the plugin signals a retry,
flb_output_thread_retries_add()is called to re-schedule the task after a delay, using theflb_scheduler.
This separation ensures that slow network destinations or I/O-heavy operations in one output plugin do not impact the performance of data collection or other output plugins.
Sources: source/src/flb_output_thread.c:283-363, 401-440
The following diagram shows the logic for an output thread.
Key Data Structures
| Structure | Purpose | Source File |
|---|---|---|
struct flb_coro | Represents a single coroutine, containing its stack, context, and state. | source/src/flb_coro.c:69-76 |
struct flb_scheduler | Manages all timers in the system using a binary heap for efficiency. | source/src/flb_scheduler.c:64-71 |
struct flb_input_thread | A context for an input plugin's collection coroutine, holding a pointer to the coroutine and the scheduler. | source/src/flb_input_thread.c:28-36 |
struct flb_output_thread_instance | Represents a running output plugin thread, containing its thread ID, context, and a link to pending tasks. | source/src/flb_output_thread.c:84-93 |
Conclusion
The FluentDo Agent's threading and concurrency model is a hybrid approach that leverages the strengths of both coroutines and traditional OS threads. Coroutines provide a lightweight mechanism for managing thousands of concurrent, non-blocking tasks like data collection. Dedicated OS threads for output plugins isolate I/O-bound operations, preventing them from blocking the core engine. This entire system is orchestrated by an efficient, timer-based scheduler, resulting in a highly scalable and performant data pipeline. This architecture is crucial for meeting the agent's design goals of high throughput and a small operational footprint.
Sources: source/README.md