| Crates.io | duroxide |
| lib.rs | duroxide |
| version | 0.1.14 |
| created_at | 2025-11-30 07:28:57.603035+00 |
| updated_at | 2026-01-25 00:09:09.062218+00 |
| description | Durable code execution framework for Rust |
| homepage | https://github.com/affandar/duroxide |
| repository | https://github.com/affandar/duroxide |
| max_upload_size | |
| id | 1957984 |
| size | 3,849,745 |

Notice: Experimental, not intended for production use yet
A lightweight and embeddable durable execution runtime for Rust. Inspired by the Durable Task Framework and Temporal.
Latest Release: v0.1.14 — Bug fix: fire-and-forget orchestrations (
schedule_orchestration) now correctly record history events for determinism. See CHANGELOG.md for release notes.
What you can build with this
ctx.schedule_timer() for orchestration-level delays. Activities can sleep/poll internally as part of their work (e.g., provisioning resources).ctx.schedule_activity_with_retry() with configurable backoff (exponential, linear, fixed) and per-attempt timeouts.ActivityContext when orchestration is cancelled; activities can clean up gracefully or be forcibly aborted after a grace period.These patterns are enabled by deterministic replay, correlation IDs, durable timers, and external event handling.
Getting started samples
docs/ORCHESTRATION-GUIDE.md for complete guide to writing workflowscargo run --example hello_world to see Duroxide in actiontests/e2e_samples.rs for comprehensive usage patternsdocs/provider-implementation-guide.md for building custom providersdocs/provider-testing-guide.md for testing custom providersdocs/observability-guide.md for structured logging and metricsAI-assisted development
docs/skills/ for AI assistant context files.github/copilot-instructions.md for Copilot, .cursor/rules for Cursor)What it is
Provider trait (SQLite provider with in-memory and file-based modes)How it works (brief)
ActivityScheduled) and completions are matched by id (e.g., ActivityCompleted).ctx.select2, ctx.select(Vec), and ctx.join(Vec) resolve by earliest completion index in history (not polling order).tracing and the ctx.trace_* helpers (implemented as a deterministic system activity). We do not persist trace events in history.Key types
OrchestrationContext: schedules work (schedule_activity, schedule_timer, schedule_wait, schedule_sub_orchestration, schedule_orchestration) and exposes deterministic select2/select/join, trace_*, continue_as_new.Event/Action: immutable history entries and host-side actions, including ContinueAsNew.Provider: persistence + queues abstraction with atomic operations and lock renewal (SqliteProvider with in-memory and file-based modes).RuntimeOptions: configure concurrency, lock timeouts, and lock renewal buffer for long-running activities.OrchestrationRegistry / ActivityRegistry: register orchestrations/activities in-memory.Project layout
src/lib.rs — orchestration primitives and single-turn executorsrc/runtime/ — runtime, registries, workers, and polling enginesrc/providers/ — SQLite history store with transactional supporttests/ — unit and e2e tests (see e2e_samples.rs to learn by example)Install (when published)
[dependencies]
duroxide = { version = "0.1", features = ["sqlite"] } # With bundled SQLite provider
# OR
duroxide = "0.1" # Core only, bring your own Provider implementation
Hello world (activities + runtime)
Note: This example uses the bundled SQLite provider. Enable the
sqlitefeature in yourCargo.toml.
use std::sync::Arc;
use duroxide::{ActivityContext, Client, ClientError, OrchestrationContext, OrchestrationRegistry, OrchestrationStatus};
use duroxide::runtime::{self};
use duroxide::runtime::registry::ActivityRegistry;
use duroxide::providers::sqlite::SqliteProvider;
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = std::sync::Arc::new(SqliteProvider::new("sqlite:./data.db", None).await?);
let activities = ActivityRegistry::builder()
.register("Hello", |ctx: ActivityContext, name: String| async move { Ok(format!("Hello, {name}!")) })
.build();
let orch = |ctx: OrchestrationContext, name: String| async move {
ctx.trace_info("hello started");
let res = ctx.schedule_activity("Hello", name).await.unwrap();
Ok::<_, String>(res)
};
let orchestrations = OrchestrationRegistry::builder().register("HelloWorld", orch).build();
let rt = runtime::Runtime::start_with_store(store.clone(), activities, orchestrations).await;
let client = Client::new(store);
client.start_orchestration("inst-hello-1", "HelloWorld", "Rust").await?; // Returns Result<(), ClientError>
match client.wait_for_orchestration("inst-hello-1", std::time::Duration::from_secs(5)).await? {
OrchestrationStatus::Completed { output } => assert_eq!(output, "Hello, Rust!"),
OrchestrationStatus::Failed { details } => panic!("Failed: {}", details.display_message()),
_ => panic!("Unexpected status"),
}
rt.shutdown(None).await; // Graceful shutdown with 1s timeout
# Ok(())
# }
Parallel fan-out
async fn fanout(ctx: OrchestrationContext) -> Vec<String> {
let f1 = ctx.schedule_activity("Greetings", "Gabbar");
let f2 = ctx.schedule_activity("Greetings", "Samba");
let outs = ctx.join(vec![f1, f2]).await; // history-ordered join
outs
.into_iter()
.map(|o| match o {
Ok(s) => s,
Err(e) => panic!("activity failed: {}", e),
})
.collect()
}
Control flow + timers + externals
use duroxide::Either2;
async fn control(ctx: OrchestrationContext) -> String {
let a = ctx.schedule_timer(std::time::Duration::from_millis(10));
let b = ctx.schedule_wait("Evt");
match ctx.select2(a, b).await {
Either2::First(()) => {
// timer won; fall back to waiting for the event deterministically
ctx.schedule_wait("Evt").await
}
Either2::Second(data) => data,
}
}
Error handling (Result<String, String>)
async fn comp_sample(ctx: OrchestrationContext) -> String {
match ctx.schedule_activity("Fragile", "bad").await {
Ok(v) => v,
Err(e) => {
ctx.trace_warn(format!("fragile failed error={e}"));
ctx.schedule_activity("Recover", "").await.unwrap()
}
}
}
ContinueAsNew and multi-execution
ctx.continue_as_new(new_input) to end the current execution and immediately start a fresh execution with the provided input.start_orchestration handle resolves with an empty success when ContinueAsNew occurs; the latest execution can be observed via status APIs.Status and control-plane
Client::get_orchestration_status(instance) -> Result<OrchestrationStatus, ClientError> where OrchestrationStatus is Running | Completed { output } | Failed { details: ErrorDetails } | NotFoundClient::wait_for_orchestration(instance, timeout) -> Wait for completion with timeout, returns Result<OrchestrationStatus, ClientError>list_executions, read_with_execution, etc.) for diagnostics.Instance management (deletion and pruning)
Client::delete_instance(instance, force) -> Delete instance and all sub-orchestrations (cascade). Use force=true for running instances.Client::delete_instance_bulk(filter) -> Bulk delete with filtering by IDs, age, and limits.Client::prune_executions(instance, options) -> Delete old executions while preserving current. Safe for running workflows.Client::prune_executions_bulk(filter, options) -> Bulk prune across multiple instances.Client::get_instance_tree(instance) -> Preview cascade deletion impact before deleting.force=true to delete.Error classification
ErrorDetails::category() for metrics, is_retryable() for retry logic, display_message() for loggingLocal development
cargo buildcargo test --all -- --nocapturecargo test --test e2e_samples sample_dtf_legacy_gabbar_greetings -- --nocaptureStress testing
./run-stress-tests.sh (parallel orchestrations + large payload)./run-stress-tests.sh --parallel-only or ./run-stress-tests.sh --large-payload./run-stress-tests.sh 60 (runs for 60 seconds)STRESS_TEST_MONITORING.md)./run-stress-tests.sh --track (saves to stress-test-results.md)docs/provider-testing-guide.md for comprehensive stress testing guideRuntime Configuration
RuntimeOptionsRuntimeOptions { worker_lock_timeout: Duration::from_secs(300), worker_lock_renewal_buffer: Duration::from_secs(30), ... }(timeout - buffer) for timeouts ≥15s, or 0.5 * timeout for shorter timeoutsRuntimeOptions for complete configuration optionsObservability
RuntimeOptions { observability: ObservabilityConfig { log_format: LogFormat::Compact, ... }, ... }RUST_LOG for additional targets (e.g., RUST_LOG=duroxide::runtime=debug)instance_id, execution_id, orchestration_name, activity_name for correlationobservability feature flagcargo run --example with_observability to see structured logging in actioncargo run --example metrics_cli to see observability dashboarddocs/observability-guide.md for complete guideNotes
duroxide in Rust source.Runtime::raise_event.ctx.trace_* helpers; logs are emitted through tracing at completion time (not persisted as history events).