diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..2721a40 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,513 @@ +# Arbiter API Reference + +This document provides detailed information about the Arbiter API, including classes, methods, and their parameters. + +## Table of Contents + +- [Arbiter Class](#arbiter-class) +- [Minion Class](#minion-class) +- [Task Class](#task-class) +- [TaskNode Class](#tasknode-class) +- [EventNode Classes](#eventnode-classes) + +--- + +## Arbiter Class + +The `Arbiter` class is responsible for creating, scheduling, and tracking tasks. It maintains the state of all jobs it has created and can retrieve results. + +### Constructor + +```python +Arbiter(event_node, finalizer_check_interval=10) +``` + +**Parameters:** + +- `event_node` - An EventNode instance used for communication +- `finalizer_check_interval` - Interval (in seconds) to check for finalizer tasks that need to run + +### Properties + +- `task_node` - Returns the TaskNode instance, creating and starting it if necessary + +### Methods + +#### `on_task_change(event, data)` + +Internal method to handle task status change events. + +**Parameters:** +- `event` - Event name +- `data` - Event data containing task information + +#### `wait_for_tasks(tasks)` + +Wait for a list of tasks to complete and yield their results. + +**Parameters:** +- `tasks` - List of task IDs to wait for + +**Returns:** +- Generator yielding task results as they complete + +#### `add_task(task, sync=False)` + +Add a task to the queue for execution. + +**Parameters:** +- `task` - Task instance to execute +- `sync` - If True, wait for task completion + +**Returns:** +- Generator yielding task IDs and optionally task results + +#### `apply(task_name, queue="default", tasks_count=1, task_args=None, task_kwargs=None, sync=False)` + +Create and execute a task. + +**Parameters:** +- `task_name` - Name of the task to execute +- `queue` - Worker queue to use +- `tasks_count` - Number of task instances to create +- `task_args` - Positional arguments for the task function +- `task_kwargs` - Keyword arguments for the task function +- `sync` - If True, wait for task completion + +**Returns:** +- List of task IDs or task results + +#### `kill(task_key, sync=True)` + +Stop a running task. + +**Parameters:** +- `task_key` - ID of the task to stop +- `sync` - If True, wait for the task to stop + +#### `kill_group(group_id)` + +Stop all tasks in a group. + +**Parameters:** +- `group_id` - ID of the group to stop + +#### `status(task_key)` + +Get the status of a task or group. + +**Parameters:** +- `task_key` - Task or group ID to check + +**Returns:** +- Dictionary with task or group status + +#### `close(waiting_tasks_timeout=None)` + +Clean up and stop the arbiter. + +**Parameters:** +- `waiting_tasks_timeout` - Maximum time to wait for tasks to complete + +#### `workers()` + +Get information about available workers. + +**Returns:** +- Dictionary with worker pool statistics + +#### `squad(tasks, callback=None)` + +Execute a group of tasks, ensuring enough workers are available. + +**Parameters:** +- `tasks` - List of Task instances to execute +- `callback` - Optional callback Task to execute when all tasks complete + +**Returns:** +- Group ID + +#### `group(tasks, callback=None)` + +Execute a group of tasks in any order. + +**Parameters:** +- `tasks` - List of Task instances to execute +- `callback` - Optional callback Task to execute when all tasks complete + +**Returns:** +- Group ID + +#### `pipe(tasks, persistent_args=None, persistent_kwargs=None)` + +Execute tasks sequentially, passing results between them. + +**Parameters:** +- `tasks` - List of Task instances to execute in sequence +- `persistent_args` - Arguments to pass to all tasks +- `persistent_kwargs` - Keyword arguments to pass to all tasks + +**Returns:** +- Generator yielding results as tasks complete + +--- + +## Minion Class + +The `Minion` class is responsible for executing tasks on worker nodes. + +### Constructor + +```python +Minion(event_node, queue="default") +``` + +**Parameters:** + +- `event_node` - An EventNode instance used for communication +- `queue` - Name of the worker queue this minion belongs to + +### Properties + +- `task_node` - Returns the TaskNode instance, creating and starting it if necessary + +### Methods + +#### `wait_for_tasks(tasks)` + +Wait for tasks to complete and yield their results. + +**Parameters:** +- `tasks` - List of task IDs to wait for + +**Returns:** +- Generator yielding task results + +#### `add_task(task, sync=False)` + +Add a task to the queue for execution. + +**Parameters:** +- `task` - Task instance to execute +- `sync` - If True, wait for task completion + +**Returns:** +- Generator yielding task IDs and optionally results + +#### `apply(task_name, queue=None, tasks_count=1, task_args=None, task_kwargs=None, sync=True)` + +Create and execute a task. + +**Parameters:** +- `task_name` - Name of the task to execute +- `queue` - Worker queue to use (defaults to minion's queue) +- `tasks_count` - Number of task instances to create +- `task_args` - Positional arguments for the task function +- `task_kwargs` - Keyword arguments for the task function +- `sync` - If True, wait for task completion (default True) + +**Returns:** +- Generator yielding task IDs and results + +#### `task(*args, **kwargs)` + +Decorator for registering task functions. + +**Usage:** +```python +@minion.task(name="task_name") +def my_function(arg1, arg2): + pass +``` + +#### `run(workers, block=True)` + +Start the minion with specified number of worker slots. + +**Parameters:** +- `workers` - Number of concurrent tasks this minion can process +- `block` - If True, block until stopped + +--- + +## Task Class + +The `Task` class represents a unit of work to be executed. + +### Constructor + +```python +Task(name, queue='default', tasks_count=1, task_key="", task_type="task", task_args=None, task_kwargs=None, callback=False, callback_queue=None, timeout=-1) +``` + +**Parameters:** + +- `name` - Name of the task function +- `queue` - Worker queue for this task +- `tasks_count` - Number of task instances to create +- `task_key` - Optional unique identifier +- `task_type` - Type of task ("task", "callback", "finalize") +- `task_args` - Positional arguments for the task function +- `task_kwargs` - Keyword arguments for the task function +- `callback` - If True, this task is a callback +- `callback_queue` - Queue for callback results +- `timeout` - Timeout in seconds (for finalize tasks) + +### Methods + +#### `to_json()` + +Convert the task to a JSON-serializable dictionary. + +**Returns:** +- Dictionary representing the task + +--- + +## TaskNode Class + +The `TaskNode` class is the core execution engine for tasks. + +### Constructor + +```python +TaskNode(event_node, pool=None, task_limit=None, ident_prefix="", multiprocessing_context="fork", kill_on_stop=False, task_retention_period=3600, housekeeping_interval=60, start_max_wait=3, query_wait=3, watcher_max_wait=3, stop_node_task_wait=3, result_max_wait=3, tmp_path="/tmp/tasknode", result_transport="memory", start_attempts=3, thread_scan_interval=1, task_approver=None) +``` + +**Parameters:** + +- `event_node` - EventNode instance for communication +- `pool` - Worker pool name +- `task_limit` - Maximum number of concurrent tasks +- `ident_prefix` - Prefix for node identifiers +- `multiprocessing_context` - Context for task execution ("fork", "spawn", "threading") +- `kill_on_stop` - If True, kill tasks when stopping +- `task_retention_period` - How long to keep task records +- `housekeeping_interval` - Interval for cleanup operations +- `start_max_wait` - Maximum wait time for task start +- `query_wait` - Wait time for state queries +- `watcher_max_wait` - Maximum wait time for task watchers +- `stop_node_task_wait` - Wait time when stopping node tasks +- `result_max_wait` - Maximum wait time for task results +- `tmp_path` - Path for temporary files +- `result_transport` - How to transport results ("memory", "files", "events") +- `start_attempts` - Number of attempts to start a task +- `thread_scan_interval` - Interval for thread scanning +- `task_approver` - Function to approve task execution + +### Methods + +#### `start(block=False)` + +Start the task node. + +**Parameters:** +- `block` - If True, block until stopped + +#### `stop(block=True)` + +Stop the task node. + +**Parameters:** +- `block` - If True, wait for tasks to complete + +#### `register_task(func, name=None, approver=None)` + +Register a task function. + +**Parameters:** +- `func` - Task function to register +- `name` - Name to register the task as +- `approver` - Function to approve task execution + +#### `unregister_task(func=None, name=None)` + +Unregister a task function. + +**Parameters:** +- `func` - Task function to unregister +- `name` - Name of the task to unregister + +#### `start_task(name, args=None, kwargs=None, pool=None, meta=None, durable=False)` + +Start a task execution. + +**Parameters:** +- `name` - Name of the task to execute +- `args` - Positional arguments +- `kwargs` - Keyword arguments +- `pool` - Worker pool to use +- `meta` - Task metadata +- `durable` - If True, task persists after node restart + +**Returns:** +- Task ID + +#### `stop_task(task_id)` + +Stop a running task. + +**Parameters:** +- `task_id` - ID of the task to stop + +#### `wait_for_task(task_id, timeout=None)` + +Wait for a task to complete. + +**Parameters:** +- `task_id` - ID of the task to wait for +- `timeout` - Maximum wait time + +#### `join_task(task_id, timeout=None)` + +Wait for a task and get its result. + +**Parameters:** +- `task_id` - ID of the task to join +- `timeout` - Maximum wait time + +**Returns:** +- Task result + +#### `get_task_status(task_id)` + +Get a task's status. + +**Parameters:** +- `task_id` - ID of the task + +**Returns:** +- Status string ("pending", "running", "stopped") + +#### `get_task_meta(task_id)` + +Get a task's metadata. + +**Parameters:** +- `task_id` - ID of the task + +**Returns:** +- Task metadata dictionary + +#### `get_task_result(task_id)` + +Get a task's result. + +**Parameters:** +- `task_id` - ID of the task + +**Returns:** +- Task result + +#### `subscribe_to_task_statuses(func)` + +Subscribe to task status changes. + +**Parameters:** +- `func` - Callback function for status changes + +#### `query_task_state(task_id=None)` + +Query the state of a task. + +**Parameters:** +- `task_id` - ID of the task to query + +#### `query_pool_state(pool=None)` + +Query the state of a worker pool. + +**Parameters:** +- `pool` - Name of the pool to query + +#### `count_free_workers(pool=None)` + +Count available workers in a pool. + +**Parameters:** +- `pool` - Name of the pool to query + +**Returns:** +- Number of available workers + +--- + +## EventNode Classes + +### EventNodeBase + +Base class for all event node implementations. + +#### Constructor + +```python +EventNodeBase(hmac_key=None, hmac_digest="sha512", callback_workers=1, log_errors=True) +``` + +**Parameters:** + +- `hmac_key` - Key for message authentication +- `hmac_digest` - Digest algorithm for authentication +- `callback_workers` - Number of callback worker threads +- `log_errors` - If True, log callback errors + +#### Methods + +- `clone()` - Create a new event node with same configuration +- `start(emit_only=False)` - Start the event node +- `stop()` - Stop the event node +- `subscribe(event_name, callback)` - Subscribe to events +- `unsubscribe(event_name, callback)` - Unsubscribe from events +- `emit(event_name, payload=None)` - Emit an event + +### RedisEventNode + +Event node using Redis as transport. + +#### Constructor + +```python +RedisEventNode(host="localhost", port=6379, db=0, password="", event_queue="tasks", **kwargs) +``` + +**Parameters:** + +- `host` - Redis server hostname +- `port` - Redis server port +- `db` - Redis database +- `password` - Redis password +- `event_queue` - Event queue name +- `**kwargs` - Additional parameters for EventNodeBase + +### SocketIOEventNode + +Event node using SocketIO as transport. + +#### Constructor + +```python +SocketIOEventNode(url="http://localhost:8080", **kwargs) +``` + +**Parameters:** + +- `url` - SocketIO server URL +- `**kwargs` - Additional parameters for EventNodeBase + +### RabbitMQEventNode + +Event node using RabbitMQ as transport. + +#### Constructor + +```python +RabbitMQEventNode(host="localhost", port=5672, credentials=None, exchange="arbiter", **kwargs) +``` + +**Parameters:** + +- `host` - RabbitMQ server hostname +- `port` - RabbitMQ server port +- `credentials` - Authentication credentials +- `exchange` - Exchange name +- `**kwargs` - Additional parameters for EventNodeBase \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..802e93c --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,201 @@ +# Arbiter Architecture Documentation + +## Overview + +Arbiter is a distributed task queue system built on Redis that facilitates the execution of tasks across a network of workers. It consists of two primary components: the **Arbiter** (job scheduler) and the **Minion** (worker). The system uses event-driven communication via Redis to coordinate task distribution and execution. + +## Core Components + +### 1. Arbiter + +The Arbiter is responsible for: +- Creating and scheduling tasks +- Tracking task state and status +- Managing task groups and execution patterns +- Handling callbacks and finalizers +- Monitoring worker availability + +The Arbiter maintains the state of all tasks in the system and can retrieve results once tasks are completed. + +### 2. Minion + +The Minion is a worker process that: +- Registers available task functions +- Executes tasks from specified queues +- Reports task results back to the Arbiter +- Manages worker resources and limits + +### 3. Task + +A Task represents a unit of work to be executed. Tasks have the following properties: +- Name: Identifies the function to execute +- Queue: Specifies which worker pool should handle the task +- Arguments: Data passed to the task function +- Type: Classification of the task (regular, callback, finalizer) +- Metadata: Additional task information + +### 4. TaskNode + +The TaskNode is the execution engine that: +- Handles task registration +- Starts and stops task execution +- Manages task state and results +- Provides task synchronization mechanisms +- Coordinates worker pools + +TaskNode implements different execution modes using either threading or multiprocessing. + +### 5. EventNode + +The EventNode provides the communication layer that: +- Implements a publish/subscribe mechanism +- Handles event routing between components +- Manages event serialization/deserialization +- Supports security through HMAC authentication + +## Communication Flow + +1. The Arbiter creates tasks and sends them to Redis +2. Minions poll Redis for tasks in their assigned queues +3. When a Minion finds a task, it executes it +4. Results are returned to Redis +5. The Arbiter retrieves and processes results + +## Task Execution Patterns + +Arbiter supports several task execution patterns: + +### Single Task + +```python +arbiter.apply("task_name", queue="default", task_args=["arg1"], task_kwargs={"key": "value"}) +``` + +### Task Group (Squad) + +A set of tasks executed together, potentially in parallel: + +```python +tasks = [Task("task1"), Task("task2")] +group_id = arbiter.squad(tasks) +``` + +### Sequential Tasks (Pipe) + +A series of tasks executed in sequence, with results from previous tasks passed to subsequent ones: + +```python +tasks = [Task("task1"), Task("task2")] +pipe_results = list(arbiter.pipe(tasks)) +``` + +### Callbacks + +Tasks executed after a group of tasks completes: + +```python +callback = Task("process_results") +group_id = arbiter.group(tasks, callback=callback) +``` + +### Finalizers + +Similar to callbacks but guaranteed to run even if the main tasks fail: + +```python +finalizer = Task("cleanup", task_type="finalize") +tasks.append(finalizer) +group_id = arbiter.group(tasks) +``` + +## Worker Pool Management + +Arbiter tracks worker availability across pools: + +```python +worker_stats = arbiter.workers() +``` + +This allows for intelligent task scheduling based on available resources. + +## Event Handling System + +The EventNode subsystem provides a flexible event handling mechanism that: +- Allows components to publish and subscribe to events +- Supports event filtering and routing +- Provides hooks for pre and post-event processing +- Handles serialization and security + +## Task Execution Process + +1. Arbiter creates a task with a unique ID +2. Task is announced to all nodes +3. Suitable worker nodes bid to execute the task +4. Arbiter selects a worker and sends task details +5. Worker executes the task +6. Results are captured and stored +7. Arbiter retrieves results + +## Concurrency Models + +Arbiter supports two concurrency models: +1. Threading: Using Python threads for I/O-bound tasks +2. Multiprocessing: Using separate processes for CPU-bound tasks + +## Error Handling + +- Task exceptions are captured and reported to the Arbiter +- Tasks can be stopped or killed if needed +- Finalizers ensure cleanup operations run even after failures + +## Task Status Lifecycle + +Tasks progress through the following states: +1. initiated - Task created and queued +2. running - Task is being executed +3. done - Task has completed or failed + +## Security + +- HMAC authentication for event messages +- Task validation through approver functions +- Isolation through separate processes + +## Architecture Diagram + +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ │ │ │ │ │ +│ Arbiter │◄──────► Redis │◄──────► Minion 1 │ +│ │ │ │ │ │ +└──────┬───────┘ └──────────────┘ └──────────────┘ + │ │ + │ │ + │ │ + │ ┌────────▼─────────┐ + │ │ │ + │ │ Task Queue 1 │ + │ │ │ + │ └──────────────────┘ + │ + │ ┌──────────────┐ + │ │ │ + └──────────────────────────────────────► Minion 2 │ + │ │ + └──────┬───────┘ + │ + │ + ┌────────▼─────────┐ + │ │ + │ Task Queue 2 │ + │ │ + └──────────────────┘ +``` + +## Design Principles + +1. **Distribution**: Tasks can be executed across multiple workers +2. **Fault Tolerance**: Tasks can be retried and monitored +3. **Flexibility**: Multiple execution patterns and worker pools +4. **Efficiency**: Optimized task routing based on worker availability +5. **Scalability**: Workers can be added or removed as needed \ No newline at end of file diff --git a/docs/TESTS.md b/docs/TESTS.md new file mode 100644 index 0000000..f32e55b --- /dev/null +++ b/docs/TESTS.md @@ -0,0 +1,153 @@ +# Arbiter Test Documentation + +This document explains the test suite for the Arbiter distributed task queue system, how to run the tests, and what each test verifies. + +## Test Environment + +The test suite uses a mock event node and in-memory task execution to test Arbiter functionality without requiring external dependencies like Redis. This makes the tests fast and self-contained. + +## Running Tests + +To run the test suite: + +```bash +# Run all tests +pytest tests/ + +# Run specific test file +pytest tests/test_arbiter.py + +# Run specific test +pytest tests/test_arbiter.py::TestArbiter::test_pipe +``` + +## Test Setup + +The test environment is set up using pytest fixtures that: + +1. Create a MockEventNode for event communication +2. Start a test Minion with predefined task functions +3. Tear down all components after tests complete + +## Test Minion + +The test Minion (`tests/minion.py`) provides several task functions for testing: + +- `simple_add`: Adds two numbers +- `add`: Adds two numbers and initiates another task +- `add_in_pipe`: Adds two numbers and a previous result (for pipeline testing) +- `long_running`: Sleeps for 180 seconds (for testing task termination) + +The Minion is configured to use threading (rather than multiprocessing) and memory transport for test efficiency. + +## Test Cases + +### test_task_in_task + +Verifies that tasks can initiate other tasks and that results are properly returned and tracked. + +1. Creates multiple "simple_add" tasks +2. Verifies tasks progress through their lifecycle states +3. Confirms correct results (1 + 2 = 3) +4. Checks that worker resources are properly released after completion + +### test_squad + +Tests the "squad" pattern (group of concurrent tasks). + +1. Creates a squad of "simple_add" tasks +2. Waits for all tasks to complete +3. Verifies that task count and completion status match expectations +4. Ensures worker resources are properly released + +### test_pipe + +Tests the "pipe" pattern (sequential task chain). + +1. Creates a pipeline of 20 "add_in_pipe" tasks +2. Verifies that results are passed correctly between tasks +3. Confirms increasing result values (4, 8, 12, etc.) +4. Ensures all tasks complete and resources are released + +### test_kill_task + +Validates task termination functionality. + +1. Starts a long-running task (180 seconds) +2. Kills the task after 2 seconds +3. Verifies the task is stopped before its natural completion +4. Ensures worker resources are properly released + +### test_kill_group + +Tests termination of an entire task group. + +1. Creates a squad of long-running tasks +2. Kills the entire group after 5 seconds +3. Verifies all tasks are stopped before natural completion +4. Ensures worker resources are properly released + +### test_squad_callback + +Tests the callback functionality for task groups. + +1. Creates a task squad with a callback +2. Verifies that the callback executes after all tasks complete +3. Confirms the callback result is correct (5 + 4 = 9) +4. Ensures worker resources are properly released + +### test_squad_finalyzer + +Tests the finalizer functionality for task groups. + +1. Creates a task squad with both a callback and finalizer +2. Verifies that both execute after all tasks complete +3. Confirms the finalizer runs last and produces correct results +4. Ensures worker resources are properly released + +### test_sync_task + +Validates synchronous task execution. + +1. Executes a "simple_add" task synchronously +2. Verifies the task completes with correct results +3. Ensures worker resources are properly released + +## Test Coverage + +The test suite covers the following aspects of Arbiter functionality: + +- Basic task execution and result retrieval +- Task lifecycle states (initiated, running, done) +- Task group execution (squad pattern) +- Sequential task execution (pipe pattern) +- Task termination (individual and group) +- Callback and finalizer functionality +- Synchronous vs. asynchronous execution +- Worker resource management +- Task-initiated subtasks + +## Mocking Strategy + +The test suite uses `MockEventNode` to simulate the event communication layer typically provided by Redis or other backends. This allows testing the core Arbiter logic without external dependencies. + +## Key Assertions + +Tests verify: + +1. **Correctness**: Task results match expected outputs +2. **State transitions**: Tasks progress properly through their lifecycle +3. **Completion**: All tasks complete (or are properly terminated) +4. **Resource management**: Worker resources are properly allocated and released +5. **Ordering**: Sequential operations happen in the correct order +6. **Timeout handling**: Tasks can be killed before natural completion + +## Writing New Tests + +When adding new tests for Arbiter, follow these guidelines: + +1. Use the existing fixture pattern for setup/teardown +2. Test one specific feature or pattern per test case +3. Include assertions for both functionality and resource management +4. Clean up resources in each test +5. Use descriptive test names that reflect what is being tested \ No newline at end of file diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 0000000..d71b754 --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,354 @@ +# Arbiter Usage Guide + +This guide provides comprehensive instructions and examples for using the Arbiter distributed task queue system. + +## Table of Contents +1. [Setup](#setup) +2. [Creating Tasks](#creating-tasks) +3. [Running Workers](#running-workers) +4. [Executing Tasks](#executing-tasks) +5. [Task Patterns](#task-patterns) +6. [Managing Task State](#managing-task-state) +7. [Error Handling](#error-handling) +8. [Advanced Features](#advanced-features) + +## Setup + +### Prerequisites +- Python 3.6+ +- Redis server + +### Installation + +Clone the repository and install the package: +```bash +git clone https://github.com/carrier-io/arbiter.git +cd arbiter +python setup.py install +``` + +### Starting Redis + +For development, you can run Redis in a Docker container: +```bash +docker run -d --rm --hostname arbiter-redis --name arbiter-redis \ + -p 6379:6379 redis:alpine redis-server +``` + +For production environments, configure a proper Redis instance with appropriate security settings. + +## Creating Tasks + +### Minion Setup + +Create a minion to define and execute tasks: + +```python +from arbiter import RedisEventNode +from arbiter import Minion + +# Configure event communication +event_node = RedisEventNode( + host="localhost", + port=6379, + password="", + event_queue="tasks" +) + +# Create a minion for the "default" queue +app = Minion(event_node, queue="default") +``` + +### Task Definition + +Define tasks using the decorator pattern: + +```python +@app.task(name="simple_add") +def add(x, y): + return x + y + +@app.task(name="process_data") +def process_data(items, multiplier=1): + return [item * multiplier for item in items] +``` + +Tasks can accept both positional and keyword arguments, and can return any serializable result. + +## Running Workers + +Start worker processes to execute tasks: + +```python +if __name__ == "__main__": + # Start 3 worker processes for this minion + app.run(workers=3) +``` + +The `workers` parameter determines how many concurrent tasks this minion can process. + +## Executing Tasks + +### Arbiter Setup + +Create an Arbiter to schedule and track tasks: + +```python +from arbiter import RedisEventNode +from arbiter import Arbiter + +# Configure event communication (same as minion) +event_node = RedisEventNode( + host="localhost", + port=6379, + password="", + event_queue="tasks" +) + +# Create the arbiter +arbiter = Arbiter(event_node) +``` + +### Basic Task Execution + +Execute a simple task: + +```python +# Execute the 'simple_add' task with arguments 5 and 3 +task_ids = arbiter.apply( + "simple_add", + task_args=[5, 3], + queue="default" +) + +# Get the result +result = arbiter.status(task_ids[0]) +print(f"Result: {result['result']}") # Output: Result: 8 +``` + +### Synchronous Execution + +Wait for task completion: + +```python +task_ids = arbiter.apply( + "simple_add", + task_args=[10, 20], + sync=True # Wait for completion +) + +# The result is directly available +print(f"Result: {task_ids[1]['result']}") # Output: Result: 30 +``` + +### Multiple Task Instances + +Execute the same task multiple times: + +```python +task_ids = arbiter.apply( + "simple_add", + task_args=[5, 5], + tasks_count=5 # Execute this task 5 times +) + +# Wait for all tasks to complete +for result in arbiter.wait_for_tasks(task_ids): + print(f"Task completed with result: {result['result']}") +``` + +## Task Patterns + +### Task Group (Squad) + +Execute a group of tasks together, ensuring enough workers are available: + +```python +from arbiter import Task + +# Define the tasks +task1 = Task("process_data", task_args=[[1, 2, 3]]) +task2 = Task("process_data", task_args=[[4, 5, 6]], task_kwargs={"multiplier": 2}) +task3 = Task("simple_add", task_args=[7, 8]) + +# Create a squad (ensures sufficient workers) +group_id = arbiter.squad([task1, task2, task3]) + +# Check group status +status = arbiter.status(group_id) +print(f"Group status: {status['state']}") # initiated, running, or done + +# Wait until done +while arbiter.status(group_id)['state'] != 'done': + time.sleep(1) + +# Get results from the group +results = [arbiter.status(task_id) for task_id in arbiter.group_state[group_id]] +``` + +### Task Pipeline (Pipe) + +Execute tasks sequentially, passing results between them: + +```python +from arbiter import Task + +# Define tasks for the pipeline +task1 = Task("process_data", task_args=[[1, 2, 3, 4, 5]]) +task2 = Task("process_data", task_kwargs={"multiplier": 2}) # Will receive task1's output + +# Execute tasks in sequence +results = [] +for result in arbiter.pipe([task1, task2]): + results.append(result) +``` + +### Callbacks + +Execute a task after a group completes: + +```python +from arbiter import Task + +# Define the main tasks +tasks = [ + Task("process_data", task_args=[[1, 2, 3]]), + Task("simple_add", task_args=[10, 20]) +] + +# Define a callback task +callback = Task("aggregate_results") + +# Execute with callback +group_id = arbiter.group(tasks, callback=callback) +``` + +### Finalizers + +Execute a task when all others complete, regardless of success/failure: + +```python +from arbiter import Task + +# Define the main tasks +tasks = [ + Task("process_data", task_args=[[1, 2, 3]]), + Task("simple_add", task_args=[10, 20]) +] + +# Define a finalizer task +cleanup = Task("cleanup_resources", task_type="finalize") +tasks.append(cleanup) + +# Execute with finalizer +group_id = arbiter.group(tasks) +``` + +## Managing Task State + +### Checking Task Status + +```python +# Check status of a single task +status = arbiter.status(task_id) +print(f"Status: {status['state']}") # initiated, running, or done + +if status['state'] == 'done': + print(f"Result: {status['result']}") +``` + +### Checking Worker Status + +```python +# Get status of all worker pools +workers = arbiter.workers() +print(workers) # Shows active, available, and total workers for each pool +``` + +### Stopping Tasks + +```python +# Kill a running task +arbiter.kill(task_id) + +# Kill an entire task group +arbiter.kill_group(group_id) +``` + +### Cleanup + +```python +# Close the arbiter when done +arbiter.close() +``` + +## Error Handling + +Tasks that raise exceptions will have the exception stored in the result: + +```python +@app.task(name="failing_task") +def failing_task(): + raise ValueError("Something went wrong!") + +# Execute the failing task +task_ids = arbiter.apply("failing_task", sync=True) + +# The exception will be raised when accessing the result +try: + result = task_ids[1]['result'] +except Exception as e: + print(f"Task failed: {e}") +``` + +## Advanced Features + +### Task Timeout + +Set a timeout for finalizer tasks: + +```python +cleanup = Task( + "cleanup", + task_type="finalize", + timeout=60 # Run the finalizer after 60 seconds if tasks are still running +) +``` + +### Custom Task Metadata + +```python +task = Task( + "process_data", + task_args=[[1, 2, 3]], + task_kwargs={"meta": {"job_id": "12345"}} +) +``` + +### Using Different Worker Queues + +```python +# Create a minion for a specific queue +cpu_minion = Minion(event_node, queue="cpu_intensive") + +# Execute a task on a specific queue +task_ids = arbiter.apply( + "process_data", + task_args=[[1, 2, 3, 4, 5]], + queue="cpu_intensive" +) +``` + +### Customizing Worker Properties + +```python +# Set custom properties on the minion +cpu_minion = Minion( + event_node, + queue="cpu_intensive" +) + +# Configure task execution +cpu_minion.raw_task_node.kill_on_stop = True # Force-kill stuck tasks +cpu_minion.raw_task_node.task_limit = 2 # Maximum concurrent tasks +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..1ead2e0 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,111 @@ +# Arbiter Documentation + +## Overview + +Arbiter is a distributed task queue system using Redis as a broker. It consists of two main components: + +1. **Arbiter**: The job scheduler that maintains the state of all jobs and retrieves results. +2. **Minion**: The worker component that executes tasks. + +This documentation provides comprehensive information about the Arbiter system architecture, usage patterns, API reference, and testing procedures. + +## Contents + +### Architecture + +- [Architecture Overview](ARCHITECTURE.md): Detailed explanation of the Arbiter system architecture, including components, communication flow, and design principles. + +### User Guides + +- [Usage Guide](USAGE.md): Comprehensive guide on how to use Arbiter, including setup, task creation, running workers, executing tasks, and advanced features. + +### API Reference + +- [API Reference](API_REFERENCE.md): Detailed documentation of all classes, methods, and their parameters in the Arbiter system. + +### Testing + +- [Test Documentation](TESTS.md): Information about the test suite, how to run tests, and what each test verifies. + +## Features + +- Distributed task execution across worker nodes +- Multiple worker pools with different capabilities +- Task synchronization and status tracking +- Various task patterns: + - Single tasks + - Task groups ("squad") + - Sequential tasks ("pipe") + - Callbacks and finalizers +- Worker management and monitoring +- Task termination and cleanup +- Event-driven communication via Redis + +## Requirements + +- Python 3.6+ +- Redis server +- Required Python packages (see requirements.txt) + +## Installation + +```bash +git clone https://github.com/carrier-io/arbiter.git +cd arbiter +python setup.py install +``` + +## Quick Start + +### Start a Minion (Worker) + +```python +from arbiter import RedisEventNode, Minion + +# Configure event node +event_node = RedisEventNode( + host="localhost", + port=6379, + password="", + event_queue="tasks" +) + +# Create minion +app = Minion(event_node, queue="default") + +# Define a task +@app.task(name="add") +def add(x, y): + return x + y + +# Start worker with 3 slots +app.run(workers=3) +``` + +### Use Arbiter (Client) + +```python +from arbiter import RedisEventNode, Arbiter + +# Configure event node (same parameters as minion) +event_node = RedisEventNode( + host="localhost", + port=6379, + password="", + event_queue="tasks" +) + +# Create arbiter +arbiter = Arbiter(event_node) + +# Execute task +task_ids = arbiter.apply("add", task_args=[5, 3]) + +# Wait for and print result +for message in arbiter.wait_for_tasks(task_ids): + print(f"Result: {message['result']}") # Output: Result: 8 +``` + +## License + +Arbiter is licensed under the Apache License 2.0 - see the LICENSE file for details. \ No newline at end of file