executor-core/sdk/retry.rs adds the retry:: namespace with:
retry::policy(opts) -> Policy
retry::on_codes(policy, codes) -> Policy
retry::run(policy, closure) -> Dynamic (closure's return value)
NOTE: the brief specified retry::with(...), but `with` is a Rhai
reserved keyword (parse error before registration even matters). `call`
is also reserved. Shipping retry::run instead — flagged in HANDBACK §7.
Policy:
- max_attempts clamped to [1, 20]
- base_ms clamped to [1, 60_000]
- jitter_pct clamped to [0, 100]
- backoff ∈ {"exponential" | "linear" | "constant"} — bogus values
surface a clear error
- on_codes is an empty default ("retry any throw"); when non-empty,
only error messages containing one of the codes retry — others
surface immediately
retry::run uses NativeCallContext + FnPtr::call_within_context to
re-invoke the closure inside the caller's engine. Closures share the
caller's cx (Rhai semantics). If the closure calls invoke(), the inner
call gets a fresh cx with trigger_depth + 1 via the invoke bridge —
retry doesn't see or interfere with that.
Sleep between attempts: tokio::time::sleep through TokioHandle::block_on.
Safe because we're already inside the caller's spawn_blocking thread.
register_all gains retry::register positionally between queue and
secrets.
Unit tests (7): policy default, max_attempts clamping (0→1, 999→20),
base_ms + jitter clamping, bogus backoff reject, exponential doubles,
linear sums, constant.
Integration tests (sdk_retry.rs, 6 binaries):
- succeeds first try (returns 42)
- max_attempts exhausted surfaces last error ("boom")
- on_codes filters — non-matching error throws immediately
- jitter clamping is silent (script still runs)
- bogus backoff string surfaces a clear error
- "fails 2 then succeeds" — counter mutates across retries, 3rd
attempt returns 3 (validates Rhai closure-capture semantics across
re-invocations through NativeCallContext)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2.3 KiB
Rust
69 lines
2.3 KiB
Rust
//! SDK plumbing — types and the per-call registration entry point.
|
|
//!
|
|
//! `executor-core` is responsible for building the per-invocation Rhai
|
|
//! engine and wiring stateful services into it. v1.1.0 ships the
|
|
//! shapes (`Services` bundle, `SdkCallCx`, `register_all` entry point)
|
|
//! but no actual services — subsequent v1.1.x PRs (KV in v1.1.1,
|
|
//! docs in v1.1.2, …) extend `register_all` rather than re-threading
|
|
//! plumbing through `engine.rs`.
|
|
//!
|
|
//! Bridge functions (`json_to_dynamic` / `dynamic_to_json`) also live
|
|
//! here so service modules can convert values without `engine.rs`
|
|
//! being the only home for the conversion logic.
|
|
|
|
pub mod bridge;
|
|
pub mod cx;
|
|
pub mod dead_letters;
|
|
pub mod docs;
|
|
pub mod email;
|
|
pub mod files;
|
|
pub mod http;
|
|
pub mod invoke;
|
|
pub mod kv;
|
|
pub mod pubsub;
|
|
pub mod queue;
|
|
pub mod retry;
|
|
pub mod secrets;
|
|
pub mod stdlib;
|
|
pub mod users;
|
|
|
|
pub use bridge::{dynamic_to_json, json_to_dynamic};
|
|
pub use cx::SdkCallCx;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use picloud_shared::Services;
|
|
use rhai::Engine as RhaiEngine;
|
|
|
|
use crate::engine::Engine;
|
|
use crate::sandbox::Limits;
|
|
|
|
/// Single hook every v1.1.x stateful service registers into. Called
|
|
/// once per invocation, just after `build_engine` constructs the
|
|
/// sandboxed Rhai engine and just before script compilation.
|
|
///
|
|
/// v1.1.9 adds the `limits` + `self_engine` parameters needed by the
|
|
/// `invoke` bridge for synchronous re-entry. `self_engine` is `None`
|
|
/// in harnesses that didn't call `Engine::set_self_weak` after
|
|
/// construction; the invoke bridge surfaces a clear error in that case.
|
|
pub fn register_all(
|
|
engine: &mut RhaiEngine,
|
|
services: &Services,
|
|
cx: Arc<SdkCallCx>,
|
|
limits: Limits,
|
|
self_engine: Option<Arc<Engine>>,
|
|
) {
|
|
kv::register(engine, services, cx.clone());
|
|
docs::register(engine, services, cx.clone());
|
|
dead_letters::register(engine, services, cx.clone());
|
|
http::register(engine, services, cx.clone());
|
|
files::register(engine, services, cx.clone());
|
|
pubsub::register(engine, services, cx.clone());
|
|
queue::register(engine, services, cx.clone());
|
|
retry::register(engine, services, cx.clone());
|
|
secrets::register(engine, services, cx.clone());
|
|
email::register(engine, services, cx.clone());
|
|
users::register(engine, services, cx.clone());
|
|
invoke::register(engine, services, cx, limits, self_engine);
|
|
}
|