feat(v1.1.9): invoke:: Rhai SDK module — sync re-entry + invoke_async
executor-core/sdk/invoke.rs adds `invoke()` and `invoke_async()` as
top-level Rhai helpers (no `::` namespace, mirroring the brief):
invoke(target, args) -> Dynamic (sync, returns callee's value)
invoke_async(target, args) -> String (fire-and-forget; returns execution_id)
Sync re-entry pattern:
- bridge captures Arc<Engine> via Engine::self_arc()
- calls engine.execute(&source, req) directly — same engine instance,
same Services, same Limits; only SdkCallCx changes per call
- runs inside the caller's spawn_blocking thread (no nested
spawn_blocking, no gate re-admission — the outer execution already
holds a permit)
Back-reference plumbing:
- Engine gains self_weak: OnceLock<Weak<Engine>> + set_self_weak() +
self_arc() accessor
- picloud binary calls engine.set_self_weak(Arc::downgrade(&engine))
right after construction
- register_all extended with limits + Option<Arc<Engine>> params
- Engine::execute_ast threads both through
Limits.trigger_depth_max added — mirrors TriggerConfig::max_trigger_depth.
Default 8; the picloud binary syncs from TriggerConfig::from_env() so
PICLOUD_MAX_TRIGGER_DEPTH governs both the dispatcher's fan-out cap and
the invoke depth bound.
Target parsing (string-first):
- "/api/foo" → InvokeTarget::Path
- 36-char UUID-like → InvokeTarget::Id (uuid::Uuid parse-validated)
- everything else → InvokeTarget::Name
Cross-app + depth + FnPtr guards:
- resolve() returns InvokeError::CrossApp if resolved.app_id != cx.app_id
- bridge throws "invoke: depth limit exceeded (max N)" when
cx.trigger_depth + 1 > limits.trigger_depth_max (checked BEFORE
resolve to avoid a wasted DB round-trip)
- args_to_json rejects FnPtr at any depth — closures don't survive
invoke boundaries
invoke_async path:
- calls services.invoke.enqueue_async() which writes an
OutboxSourceKind::Invoke row (commit 10 wires the dispatcher arm)
- returns the new ExecutionId as a string for caller tracking
Integration tests (sdk_invoke.rs, 6 binaries):
- sync invoke returns callee's value (script returns body.x + 1)
- cross-app invoke rejected (target in other app)
- depth limit exceeded (recursive script that calls itself; throws
before stack overflow)
- callee receives incremented depth in cx (smoke — strict assertion
needs ctx.trigger_depth surface, deferred to v1.2)
- args_to_json rejects FnPtr in payload
- invoke_async returns valid UUID string + queues args payload
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ pub mod docs;
|
||||
pub mod email;
|
||||
pub mod files;
|
||||
pub mod http;
|
||||
pub mod invoke;
|
||||
pub mod kv;
|
||||
pub mod pubsub;
|
||||
pub mod queue;
|
||||
@@ -33,13 +34,24 @@ 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.1 wires the first stateful service (KV). Subsequent PRs add a
|
||||
/// single `<service>::register(...)` line per service.
|
||||
pub fn register_all(engine: &mut RhaiEngine, services: &Services, cx: Arc<SdkCallCx>) {
|
||||
/// 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());
|
||||
@@ -49,5 +61,6 @@ pub fn register_all(engine: &mut RhaiEngine, services: &Services, cx: Arc<SdkCal
|
||||
queue::register(engine, services, cx.clone());
|
||||
secrets::register(engine, services, cx.clone());
|
||||
email::register(engine, services, cx.clone());
|
||||
users::register(engine, services, cx);
|
||||
users::register(engine, services, cx.clone());
|
||||
invoke::register(engine, services, cx, limits, self_engine);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user