Seperti Claude Code: satu runtime shared, concurrency dibatasi, error subagent terisolasi (satu node gagal tidak menggagalkan cycle). - feat(runtime): global tokio runtime via OnceLock — ganti 9+ titik Runtime::new() per tool call (spawn, parallel_delegate, workflow, explore, dir_cache, daemon handler). Hemat resource, hilangkan panic path Runtime::new().expect() di daemon compaction. - fix(workflow): execute_cycle ganti try_join_all (fail-fast) → buffer_unordered(8) + isolasi error per node; node gagal di-log dan diganti [ERROR], hasil node lain tetap dipakai (Claude Code-style). - fix(parallel_delegate): spawn subagent dibatasi per batch max_parallel (tidak unbounded threads). - perf(subagent): run_agent adaptif max_tokens (800/1600/4096), temp 0.2, truncate tool output 12k, error-recovery note utk tool error berulang. - test: runtime singleton + block_on (2 test).
63 lines
2.3 KiB
Rust
63 lines
2.3 KiB
Rust
//! Process-wide shared Tokio runtime for sync → async bridging.
|
|
//!
|
|
//! Many `Tool::run` implementations are synchronous but need to drive async
|
|
//! work (LLM calls, subagent execution). Creating a fresh
|
|
//! [`tokio::runtime::Runtime`] on every call is expensive (spawns a thread
|
|
//! pool + runtime each time) and can fail randomly under thread pressure.
|
|
//!
|
|
//! # Flow
|
|
//!
|
|
//! [`runtime()`] returns a lazily-initialised process-wide runtime created
|
|
//! exactly once via [`std::sync::OnceLock`]. Callers use
|
|
//! `runtime().block_on(...)` exactly like they would with a local runtime —
|
|
//! the only difference is the runtime is shared, so the cost is paid once per
|
|
//! process instead of once per tool call.
|
|
//!
|
|
//! # Safety
|
|
//!
|
|
//! `block_on` panics if called from within a running Tokio runtime. The
|
|
//! tools that use this helper are synchronous (`Tool::run`), so this is safe
|
|
//! in practice. Async code should never call `runtime().block_on`.
|
|
|
|
use std::sync::OnceLock;
|
|
|
|
/// Maximum worker threads for the shared runtime. Kept modest — tools are
|
|
/// mostly I/O-bound and rarely need more concurrency than this.
|
|
const RUNTIME_WORKER_THREADS: usize = 8;
|
|
|
|
static SHARED_RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
|
|
|
/// Return the process-wide shared Tokio runtime, initialising it on first use.
|
|
///
|
|
/// The runtime is configured with `worker_threads = 8` and
|
|
/// `enable_all()` (time + IO drivers) so streams, timers, and network calls
|
|
/// all work. If initialisation fails (extremely rare — resource exhaustion at
|
|
/// startup), the process aborts with a clear message rather than returning
|
|
/// an error on every subsequent call.
|
|
pub fn runtime() -> &'static tokio::runtime::Runtime {
|
|
SHARED_RUNTIME.get_or_init(|| {
|
|
tokio::runtime::Builder::new_multi_thread()
|
|
.worker_threads(RUNTIME_WORKER_THREADS)
|
|
.thread_name("zesdex-shared-rt")
|
|
.enable_all()
|
|
.build()
|
|
.expect("failed to create shared tokio runtime")
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::runtime;
|
|
|
|
#[test]
|
|
fn runtime_is_singleton() {
|
|
assert!(std::ptr::eq(runtime(), runtime()));
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_blocks_and_resolves() {
|
|
let val = runtime().block_on(async { 6 * 7 });
|
|
assert_eq!(val, 42);
|
|
}
|
|
}
|