feat: expose active execution telemetry
Expose the latest completed Rune step count, instruction pointer, and call-frame depth on active actor snapshots. Preserve telemetry while paused and clear it when execution ends or is cancelled. Document that this is not source-level debugging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
1916814c2e
commit
8d3496c621
|
|
@ -29,6 +29,8 @@ All notable changes to this project will be documented in this file.
|
||||||
atomic failure behavior and migration events.
|
atomic failure behavior and migration events.
|
||||||
- Explicit invocation cancellation that requeues the reserved message for
|
- Explicit invocation cancellation that requeues the reserved message for
|
||||||
retry, exposed in the CLI and documented with native side-effect caveats.
|
retry, exposed in the CLI and documented with native side-effect caveats.
|
||||||
|
- Active actor execution telemetry in snapshots: completed VM steps,
|
||||||
|
instruction pointer, and call-frame depth.
|
||||||
- Execution-model decision and Phase 1 runtime documentation.
|
- Execution-model decision and Phase 1 runtime documentation.
|
||||||
- Lifecycle tests proving failed-message retry from committed state and
|
- Lifecycle tests proving failed-message retry from committed state and
|
||||||
stop/restart preservation of reserved-first FIFO order.
|
stop/restart preservation of reserved-first FIFO order.
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ effects. Source and compiled implementation identities are BLAKE3 content
|
||||||
addresses with an in-memory compile cache. Persistence, interactive behavior
|
addresses with an in-memory compile cache. Persistence, interactive behavior
|
||||||
editing, canonicalization, the graphical world, and development API are not
|
editing, canonicalization, the graphical world, and development API are not
|
||||||
implemented yet. The code library now supports an in-memory versioned behavior
|
implemented yet. The code library now supports an in-memory versioned behavior
|
||||||
registry, and the runtime can install an implementation at an idle handler
|
registry, and the CLI exposes publishing, history, rollback, safe-boundary
|
||||||
boundary; these operations are not yet exposed in the CLI.
|
installation, pause/resume, and cancellation.
|
||||||
|
|
||||||
The prototype uses worker-local invocation stacks and cooperative instruction
|
The prototype uses worker-local invocation stacks and cooperative instruction
|
||||||
slices. Native callbacks are not preemptible.
|
slices, and actor snapshots report basic active-execution progress. Native
|
||||||
|
callbacks are not preemptible; source-level debugging and persistent storage
|
||||||
|
are not implemented.
|
||||||
|
|
||||||
## Design goals
|
## Design goals
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,18 @@ pub struct ActorSnapshot {
|
||||||
pub incarnation: u64,
|
pub incarnation: u64,
|
||||||
pub state: i64,
|
pub state: i64,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
|
pub execution: Option<ExecutionSnapshot>,
|
||||||
pub queued: Vec<Message>,
|
pub queued: Vec<Message>,
|
||||||
pub reserved: Option<Message>,
|
pub reserved: Option<Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ExecutionSnapshot {
|
||||||
|
pub vm_steps: u64,
|
||||||
|
pub instruction_pointer: usize,
|
||||||
|
pub call_depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Spawned(ActorId),
|
Spawned(ActorId),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,13 @@ use rune::{FromValue, Vm};
|
||||||
|
|
||||||
use crate::ExperimentResult;
|
use crate::ExperimentResult;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct InvocationProgress {
|
||||||
|
pub vm_steps: u64,
|
||||||
|
pub instruction_pointer: usize,
|
||||||
|
pub call_depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
/// A worker-local execution slice; native callbacks must remain nonblocking.
|
/// A worker-local execution slice; native callbacks must remain nonblocking.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Slice<T> {
|
pub enum Slice<T> {
|
||||||
|
|
@ -24,6 +31,7 @@ type ExecutionFuture<T> = Pin<Box<dyn Future<Output = ExperimentResult<T>>>>;
|
||||||
pub struct Invocation<T> {
|
pub struct Invocation<T> {
|
||||||
future: Option<ExecutionFuture<T>>,
|
future: Option<ExecutionFuture<T>>,
|
||||||
stepped: Rc<Cell<bool>>,
|
stepped: Rc<Cell<bool>>,
|
||||||
|
progress: Rc<Cell<InvocationProgress>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: FromValue + 'static> Invocation<T> {
|
impl<T: FromValue + 'static> Invocation<T> {
|
||||||
|
|
@ -31,9 +39,19 @@ impl<T: FromValue + 'static> Invocation<T> {
|
||||||
let mut execution = vm.execute([name], args)?.into_owned();
|
let mut execution = vm.execute([name], args)?.into_owned();
|
||||||
let stepped = Rc::new(Cell::new(false));
|
let stepped = Rc::new(Cell::new(false));
|
||||||
let marker = Rc::clone(&stepped);
|
let marker = Rc::clone(&stepped);
|
||||||
|
let progress = Rc::new(Cell::new(InvocationProgress::default()));
|
||||||
|
let inspector = Rc::clone(&progress);
|
||||||
let future = Box::pin(async move {
|
let future = Box::pin(async move {
|
||||||
loop {
|
loop {
|
||||||
if let Some(value) = execution.async_step().await.into_result()? {
|
let result = execution.async_step().await;
|
||||||
|
let vm = execution.vm();
|
||||||
|
let previous = inspector.get();
|
||||||
|
inspector.set(InvocationProgress {
|
||||||
|
vm_steps: previous.vm_steps.saturating_add(1),
|
||||||
|
instruction_pointer: vm.ip(),
|
||||||
|
call_depth: vm.call_frames().len(),
|
||||||
|
});
|
||||||
|
if let Some(value) = result.into_result()? {
|
||||||
return Ok(rune::from_value(value)?);
|
return Ok(rune::from_value(value)?);
|
||||||
}
|
}
|
||||||
marker.set(true);
|
marker.set(true);
|
||||||
|
|
@ -53,9 +71,15 @@ impl<T: FromValue + 'static> Invocation<T> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
future: Some(future),
|
future: Some(future),
|
||||||
stepped,
|
stepped,
|
||||||
|
progress,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get instruction telemetry from the most recently completed VM step.
|
||||||
|
pub fn progress(&self) -> InvocationProgress {
|
||||||
|
self.progress.get()
|
||||||
|
}
|
||||||
|
|
||||||
/// Bound VM steps per call, preserving the exact pending async step.
|
/// Bound VM steps per call, preserving the exact pending async step.
|
||||||
pub fn poll_slice(&mut self, cx: &mut Context<'_>, budget: NonZeroUsize) -> Slice<T> {
|
pub fn poll_slice(&mut self, cx: &mut Context<'_>, budget: NonZeroUsize) -> Slice<T> {
|
||||||
let Some(future) = self.future.as_mut() else {
|
let Some(future) = self.future.as_mut() else {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub const COMPILER_CONFIGURATION_ID: &str =
|
||||||
"default-modules; native-functions:v1:native_double,native_suspend";
|
"default-modules; native-functions:v1:native_double,native_suspend";
|
||||||
|
|
||||||
mod execution;
|
mod execution;
|
||||||
pub use execution::{Invocation, Slice};
|
pub use execution::{Invocation, InvocationProgress, Slice};
|
||||||
|
|
||||||
/// Errors remain visible to the caller; compilation also emits source diagnostics.
|
/// Errors remain visible to the caller; compilation also emits source diagnostics.
|
||||||
pub type ExperimentResult<T> = Result<T, Box<dyn Error>>;
|
pub type ExperimentResult<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::task::{Context, Wake, Waker};
|
use std::task::{Context, Wake, Waker};
|
||||||
|
|
||||||
use runa_actor::{ActorId, ActorSnapshot, Event, MailboxId, Message, Status};
|
use runa_actor::{ActorId, ActorSnapshot, Event, ExecutionSnapshot, MailboxId, Message, Status};
|
||||||
use runa_code::CompiledArtifact;
|
use runa_code::CompiledArtifact;
|
||||||
use runa_core::ImplementationId;
|
use runa_core::ImplementationId;
|
||||||
use runa_rune::{ExperimentResult, Invocation, Slice};
|
use runa_rune::{ExperimentResult, Invocation, Slice};
|
||||||
|
|
@ -82,6 +82,7 @@ impl Runtime {
|
||||||
incarnation: 1,
|
incarnation: 1,
|
||||||
state,
|
state,
|
||||||
status: Status::Idle,
|
status: Status::Idle,
|
||||||
|
execution: None,
|
||||||
queued: Vec::new(),
|
queued: Vec::new(),
|
||||||
reserved: None,
|
reserved: None,
|
||||||
},
|
},
|
||||||
|
|
@ -113,6 +114,14 @@ impl Runtime {
|
||||||
pub fn inspect(&self, id: ActorId) -> ExperimentResult<ActorSnapshot> {
|
pub fn inspect(&self, id: ActorId) -> ExperimentResult<ActorSnapshot> {
|
||||||
let actor = self.actors.get(&id).ok_or("unknown actor")?;
|
let actor = self.actors.get(&id).ok_or("unknown actor")?;
|
||||||
let mut snapshot = actor.snapshot.clone();
|
let mut snapshot = actor.snapshot.clone();
|
||||||
|
snapshot.execution = actor.active.as_ref().map(|invocation| {
|
||||||
|
let progress = invocation.progress();
|
||||||
|
ExecutionSnapshot {
|
||||||
|
vm_steps: progress.vm_steps,
|
||||||
|
instruction_pointer: progress.instruction_pointer,
|
||||||
|
call_depth: progress.call_depth,
|
||||||
|
}
|
||||||
|
});
|
||||||
snapshot.queued = actor.mailbox.iter().copied().collect();
|
snapshot.queued = actor.mailbox.iter().copied().collect();
|
||||||
Ok(snapshot)
|
Ok(snapshot)
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +131,14 @@ impl Runtime {
|
||||||
.values()
|
.values()
|
||||||
.map(|actor| {
|
.map(|actor| {
|
||||||
let mut snapshot = actor.snapshot.clone();
|
let mut snapshot = actor.snapshot.clone();
|
||||||
|
snapshot.execution = actor.active.as_ref().map(|invocation| {
|
||||||
|
let progress = invocation.progress();
|
||||||
|
ExecutionSnapshot {
|
||||||
|
vm_steps: progress.vm_steps,
|
||||||
|
instruction_pointer: progress.instruction_pointer,
|
||||||
|
call_depth: progress.call_depth,
|
||||||
|
}
|
||||||
|
});
|
||||||
snapshot.queued = actor.mailbox.iter().copied().collect();
|
snapshot.queued = actor.mailbox.iter().copied().collect();
|
||||||
snapshot
|
snapshot
|
||||||
})
|
})
|
||||||
|
|
@ -419,6 +436,41 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execution_snapshot_reports_live_progress_and_is_stable_while_paused() -> ExperimentResult<()>
|
||||||
|
{
|
||||||
|
let mut runtime = Runtime::default();
|
||||||
|
let implementation = publish(
|
||||||
|
&mut runtime,
|
||||||
|
"pub fn handle(state, kind, value) { loop {} }",
|
||||||
|
)?;
|
||||||
|
let actor = runtime.spawn(implementation, 0, capacity(2))?;
|
||||||
|
runtime.send(actor, Message { kind: 0, value: 1 })?;
|
||||||
|
runtime.tick(capacity(3));
|
||||||
|
|
||||||
|
let running = runtime.inspect(actor)?;
|
||||||
|
assert_eq!(running.status, Status::Running);
|
||||||
|
let progress = running
|
||||||
|
.execution
|
||||||
|
.ok_or("running actor has no execution snapshot")?;
|
||||||
|
assert!(progress.vm_steps > 0);
|
||||||
|
assert!(running.reserved.is_some());
|
||||||
|
|
||||||
|
runtime.pause(actor)?;
|
||||||
|
for _ in 0..5 {
|
||||||
|
runtime.tick(capacity(10));
|
||||||
|
}
|
||||||
|
let paused = runtime.inspect(actor)?;
|
||||||
|
assert_eq!(paused.status, Status::Paused);
|
||||||
|
assert_eq!(paused.execution, Some(progress));
|
||||||
|
|
||||||
|
runtime.cancel(actor)?;
|
||||||
|
let cancelled = runtime.inspect(actor)?;
|
||||||
|
assert_eq!(cancelled.execution, None);
|
||||||
|
assert_eq!(cancelled.queued, vec![Message { kind: 0, value: 1 }]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_effect_does_not_commit_state_or_partial_sends() -> ExperimentResult<()> {
|
fn invalid_effect_does_not_commit_state_or_partial_sends() -> ExperimentResult<()> {
|
||||||
let mut runtime = Runtime::default();
|
let mut runtime = Runtime::default();
|
||||||
|
|
|
||||||
|
|
@ -411,8 +411,10 @@ This is a **target layout**, not a request to generate empty crates. For Phase 0
|
||||||
The current runtime prototype supports implementation installation only at an
|
The current runtime prototype supports implementation installation only at an
|
||||||
idle handler boundary, cooperative pause/resume between scheduler ticks, and an
|
idle handler boundary, cooperative pause/resume between scheduler ticks, and an
|
||||||
explicit host-provided migration closure for its temporary `i64` actor state.
|
explicit host-provided migration closure for its temporary `i64` actor state.
|
||||||
Structured state schemas, version compatibility checks, and Rune-authored
|
Actor snapshots expose active VM instruction progress, instruction pointer,
|
||||||
migration functions remain future work.
|
and call-frame depth. Structured state schemas, version compatibility checks,
|
||||||
|
Rune-authored migration functions, source mapping, and local-variable
|
||||||
|
inspection remain future work.
|
||||||
|
|
||||||
### Phase 5 — Image API
|
### Phase 5 — Image API
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,12 @@ only if its future has been woken. These operations take effect between calls
|
||||||
to `tick` and cannot interrupt a currently executing Rune slice or native
|
to `tick` and cannot interrupt a currently executing Rune slice or native
|
||||||
callback. They provide cooperative scheduling control, not preemption.
|
callback. They provide cooperative scheduling control, not preemption.
|
||||||
|
|
||||||
|
Actor snapshots include basic execution telemetry while an invocation is
|
||||||
|
active: completed VM instruction-step count, Rune instruction pointer, and
|
||||||
|
call-frame depth. Paused invocations retain the same telemetry. These values
|
||||||
|
are execution-local diagnostics only; they do not map to source locations or
|
||||||
|
expose locals, and disappear when the invocation completes or is cancelled.
|
||||||
|
|
||||||
`cancel` is distinct from `stop`: it drops an actor's active invocation, moves
|
`cancel` is distinct from `stop`: it drops an actor's active invocation, moves
|
||||||
its reserved message to the front of the mailbox, and returns the actor to
|
its reserved message to the front of the mailbox, and returns the actor to
|
||||||
`Idle`. The input is therefore eligible for retry on the next tick, ahead of
|
`Idle`. The input is therefore eligible for retry on the next tick, ahead of
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue