108 lines
4.8 KiB
Markdown
108 lines
4.8 KiB
Markdown
# Rhai Client
|
|
|
|
The `rhai-client` crate provides a fluent builder-based interface for submitting Rhai scripts to a distributed task execution system over Redis. It enables applications to offload Rhai script execution to one or more worker services and await the results.
|
|
|
|
## Features
|
|
|
|
- **Fluent Builder API**: A `RhaiDispatcherBuilder` for easy client configuration and a `PlayRequestBuilder` for constructing and submitting script execution requests.
|
|
- **Asynchronous Operations**: Built with `tokio` for non-blocking I/O.
|
|
- **Request-Reply Pattern**: Submits tasks and awaits results on a dedicated reply queue, eliminating the need for polling.
|
|
- **Configurable Timeouts**: Set timeouts for how long the client should wait for a task to complete.
|
|
- **Direct-to-Worker-Queue Submission**: Tasks are sent to a queue named after the `worker_id`, allowing for direct and clear task routing.
|
|
- **Manual Status Check**: Provides an option to manually check the status of a task by its ID.
|
|
|
|
## Core Components
|
|
|
|
- **`RhaiDispatcherBuilder`**: A builder to construct a `RhaiDispatcher`. Requires a `caller_id` and Redis URL.
|
|
- **`RhaiDispatcher`**: The main client for interacting with the task system. It's used to create `PlayRequestBuilder` instances.
|
|
- **`PlayRequestBuilder`**: A fluent builder for creating and dispatching a script execution request. You can set:
|
|
- `worker_id`: The ID of the worker queue to send the task to.
|
|
- `script` or `script_path`: The Rhai script to execute.
|
|
- `request_id`: An optional unique ID for the request.
|
|
- `timeout`: How long to wait for a result.
|
|
- **Submission Methods**:
|
|
- `submit()`: Submits the request and returns immediately (fire-and-forget).
|
|
- `await_response()`: Submits the request and waits for the result or a timeout.
|
|
- **`RhaiTaskDetails`**: A struct representing the details of a task, including its script, status (`pending`, `processing`, `completed`, `error`), output, and error messages.
|
|
- **`RhaiDispatcherError`**: An enum for various errors, such as Redis errors, serialization issues, or task timeouts.
|
|
|
|
## How It Works
|
|
|
|
1. A `RhaiDispatcher` is created using the `RhaiDispatcherBuilder`, configured with a `caller_id` and Redis URL.
|
|
2. A `PlayRequestBuilder` is created from the client.
|
|
3. The script, `worker_id`, and an optional `timeout` are configured on the builder.
|
|
4. When `await_response()` is called:
|
|
a. A unique `task_id` (UUID v4) is generated.
|
|
b. Task details are stored in a Redis hash with a key like `rhailib:<task_id>`.
|
|
c. The `task_id` is pushed to the worker's queue, named `rhailib:<worker_id>`.
|
|
d. The client performs a blocking pop (`BLPOP`) on a dedicated reply queue (`rhailib:reply:<task_id>`), waiting for the worker to send the result.
|
|
5. A `rhai-worker` process, listening on the `rhailib:<worker_id>` queue, picks up the task, executes it, and pushes the final `RhaiTaskDetails` to the reply queue.
|
|
6. The client receives the result from the reply queue and returns it to the caller.
|
|
|
|
## Prerequisites
|
|
|
|
- A running Redis instance accessible by the client and the worker services.
|
|
|
|
## Usage Example
|
|
|
|
The following example demonstrates how to build a client, submit a script, and wait for the result.
|
|
|
|
```rust
|
|
use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError};
|
|
use std::time::Duration;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
env_logger::init();
|
|
|
|
// 1. Build the client
|
|
let client = RhaiDispatcherBuilder::new()
|
|
.caller_id("my-app-instance-1")
|
|
.redis_url("redis://127.0.0.1/")
|
|
.build()?;
|
|
|
|
// 2. Define the script and target worker
|
|
let script = r#" "Hello, " + worker_id + "!" "#;
|
|
let worker_id = "worker-1";
|
|
|
|
// 3. Use the PlayRequestBuilder to configure and submit the request
|
|
let result = client
|
|
.new_play_request()
|
|
.worker_id(worker_id)
|
|
.script(script)
|
|
.timeout(Duration::from_secs(5))
|
|
.await_response()
|
|
.await;
|
|
|
|
match result {
|
|
Ok(details) => {
|
|
log::info!("Task completed successfully!");
|
|
log::info!("Status: {}", details.status);
|
|
if let Some(output) = details.output {
|
|
log::info!("Output: {}", output);
|
|
}
|
|
}
|
|
Err(RhaiDispatcherError::Timeout(task_id)) => {
|
|
log::error!("Task {} timed out.", task_id);
|
|
}
|
|
Err(e) => {
|
|
log::error!("An unexpected error occurred: {}", e);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
Refer to the `examples/` directory for more specific use cases, such as `timeout_example.rs` which tests the timeout mechanism.
|
|
|
|
## Building and Running Examples
|
|
|
|
To run an example (e.g., `timeout_example`):
|
|
|
|
```bash
|
|
cd src/client # (or wherever this client's Cargo.toml is)
|
|
cargo run --example timeout_example
|
|
```
|
|
Ensure a Redis server is running and accessible at `redis://127.0.0.1/`.
|