Skip to content

Commit e752a26

Browse files
authored
docs: Add how it works documentation (#88)
* docs: start working on how it works section in docs * typo: fix typo in README * format: format the docs readme files * Finish worker docs * Update docs index
1 parent b652834 commit e752a26

7 files changed

Lines changed: 104 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ Running the async function in an async context will also enqueue the task.
8181

8282
## Installing the worker
8383

84-
In order the tasks to be executed you need to run a FluxQueue worker. You need to install the worker on your system, recommended way of doing that using `fluxqueue-worker`:
84+
In order the tasks to be executed you need to run a FluxQueue worker. You need to install the worker on your system, recommended way of doing that is using `fluxqueue-worker`:
8585

8686
```bash
8787
fluxqueue worker install

docs/how-it-works/client.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Client Library
2+
3+
In this section, we are going to talk about the client library. For installation, please visit the [Installation Tutorial](../tutorial/installation.md).
4+
5+
## FluxQueue class
6+
7+
When you import the `FluxQueue` class from the library and initialize it:
8+
9+
```py
10+
from fluxqueue import FluxQueue
11+
12+
fluxqueue = FluxQueue(redis_url="redis://localhost:6379")
13+
```
14+
15+
You might think that this establishes a TCP connection to the Redis server, but it does not. Internally, this only saves the `redis_url` you pass to the class, which is then used by the `enqueue` methods on the Rust side of the library.
16+
17+
## `task` decorator
18+
19+
This decorator is used to mark a function as a task function, which lets you enqueue the task by just running that function:
20+
21+
```py
22+
@fluxqueue.task()
23+
def send_email_task(email: str):
24+
send_email(email)
25+
26+
send_email_task()
27+
```
28+
29+
When the function is executed, it internally calls a Rust method that establishes a Redis connection and pushes the task into the queue. This entire process is handled on the Rust side of the library.
30+
31+
On the Python side, the only responsibility is dispatching based on the function type. If the function is asynchronous, the async Redis connection method is used, and the resulting Rust Future is converted into a Python awaitable using `pyo3_async_runtimes::tokio::future_into_py`, allowing it to be awaited in Python. If the function is synchronous, a standard (blocking) Redis connection is used instead.

docs/how-it-works/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# How it Works
2+
3+
As you may already know, FluxQueue is composed of two main components: a Python client library and a system-level worker. These components work together to enqueue and execute tasks. In this documentation we are going to talk about how they actually work.
4+
5+
## Sections
6+
7+
- **[Client Library](client.md)** - How Client library works
8+
- **[FluxQueue Worker](worker.md)** - How FluxQueue worker works

docs/how-it-works/worker.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# FluxQueue Worker
2+
3+
The FluxQueue worker is a Rust binary which internally runs a `tokio` multi-threaded runtime. The `--concurrency` argument it takes actually determines the number of `tokio` tasks it is going to spawn. That's why even with a single worker and higher `concurrency` it performs well with high concurrency while keeping memory usage low.
4+
5+
## Executor Loop
6+
7+
An executor loop is a `tokio` task that the runtime spawns for a given worker. For each unit of `concurrency`, the worker creates one executor loop that:
8+
9+
- Dequeues the next task from Redis and marks it as "processing".
10+
- Looks up the corresponding Python function in the task registry.
11+
- Executes the function and waits for it to finish.
12+
- On success, removes the task from the processing list.
13+
- On failure, marks the task as failed so it can be retried by the janitor loop.
14+
15+
The task registry itself is a shared, thread-safe registry of task functions, so all executors can look up Python callables by name without copying them. This lets multiple executor loops perform fast, concurrent lookups from the same set of registered tasks.
16+
17+
## Janitor Loop
18+
19+
In addition to the executor loops, the worker also runs a single janitor loop as another `tokio` task. The janitor loop is responsible for:
20+
21+
- Periodically checking Redis for tasks that have failed.
22+
- Deciding whether a failed task should be retried or considered "dead" based on its `retries` and `max_retries` fields.
23+
- Requeuing failed tasks back into the main queue if they still have retries left.
24+
- Optionally pushing tasks that have used all retries into a "dead tasks" list when `--save-dead-tasks` is enabled. These dead tasks are kept only for inspection and debugging and are not reprocessed by the worker.
25+
- Sending heartbeat updates for all executors so they can be tracked as healthy by the system.
26+
27+
This separation lets executor loops focus purely on running user code, while the janitor loop handles retries, cleanup, and health tracking in the background.
28+
29+
## Graceful Shutdown
30+
31+
When you stop the worker with `Ctrl+C`:
32+
33+
- The worker broadcasts a shutdown signal to all executor loops and the janitor loop.
34+
- Each loop finishes the task it is currently processing but does not take new work from Redis.
35+
- After all loops exit, the worker removes executor registrations from Redis and then terminates.
36+
- The main task queue is not removed, so a restarted worker continues processing any remaining tasks in the queue.
37+
38+
## Handling Async Tasks
39+
40+
When a task is dispatched, the worker first calls the corresponding Python function with its deserialized `args` and `kwargs`. If the function returns a regular value, it is treated as a **synchronous task** and completes on the executor loop itself.
41+
42+
If the function returns a coroutine (i.e. an `async def` in Python), it is treated as an **asynchronous task**. In that case:
43+
44+
- The coroutine object is sent to a dedicated Python dispatcher.
45+
- The dispatcher converts the Python coroutine into a Rust `Future` using `pyo3_async_runtimes::tokio::into_future`.
46+
- That future is then polled to completion on the Tokio runtime, so the async Python code runs concurrently with other tasks.
47+
48+
This design lets you freely mix sync and async Python task functions while keeping the Rust worker fully async and non-blocking.
49+
50+
## Python Dispatcher
51+
52+
The Python dispatcher is a small component that owns a dedicated Python event loop and hides the complexity of driving async Python code from Rust. Internally, it:
53+
54+
- Runs a background thread that initializes Python and starts a Tokio runtime bound to the Python interpreter.
55+
- Listens on an internal channel for incoming Python coroutine objects sent by executor loops.
56+
- For each coroutine, wraps it into a Rust `Future` via `pyo3_async_runtimes::tokio::into_future` and awaits it on the dispatcher’s Tokio runtime.
57+
58+
Because all async Python work is funneled through this dispatcher, executor loops still acquire the Python GIL when calling into Python code, but they do not need to manage a Python async event loop themselves. They send the coroutine to the dispatcher over an internal `tokio::sync::mpsc` channel and then await the result like any other async Rust function.

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Welcome to FluxQueue documentation. FluxQueue is lightweight, high-throughput ta
1313
- [Quick Start](tutorial/quickstart.md) - Your first task queue
1414
- [Defining and Exposing Tasks](tutorial/defininig_and_exposing_tasks.md) - Organize and expose tasks for the worker
1515
- [Worker Setup](tutorial/worker.md) - Deploy and run workers
16+
- [How it Works](how-it-works/index.md) - Learn more about how FluxQueue actually works
1617
- [API Reference](api/index.md) - Complete API documentation
1718

1819
## What is FluxQueue?

docs/tutorial/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Installation
22

3-
FluxQueue has two parts: the Python client library for enqueueing tasks, and the Rust worker that executes them. Both use Rust under the hood for speed. The worker needs to be installed separately on your system. Currently Linux is supported, with Windows and macOS support coming soon. The Python version range will be expanded in future releases as well.
3+
FluxQueue has two parts: the Python client library for enqueueing tasks, and the Rust worker that executes them. Both use Rust under the hood for it's low memory usage. The worker needs to be installed separately on your system. Currently Linux is supported, with Windows and macOS support coming soon. The Python version range will be expanded in future releases as well.
44

55
## Prerequisites
66

mkdocs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ nav:
6565
- tutorial/quickstart.md
6666
- tutorial/defininig_and_exposing_tasks.md
6767
- tutorial/worker.md
68+
- How it Works:
69+
- how-it-works/index.md
70+
- how-it-works/client.md
71+
- how-it-works/worker.md
6872
- API Reference:
6973
- api/index.md
7074
- api/fluxqueue.md

0 commit comments

Comments
 (0)