2026-11-02 08:19:04 +00:00
|
|
|
//! Single-worker, in-memory actor runtime. No durability or preemption guarantees.
|
|
|
|
|
|
|
|
|
|
use std::collections::{BTreeMap, VecDeque};
|
|
|
|
|
use std::num::NonZeroUsize;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
|
use std::task::{Context, Wake, Waker};
|
|
|
|
|
|
2026-11-02 14:19:25 +00:00
|
|
|
use runa_actor::{ActorRef, ActorSnapshot, Event, ExecutionSnapshot, MailboxId, Message, Status};
|
2026-11-02 09:08:49 +00:00
|
|
|
use runa_code::CompiledArtifact;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
use runa_core::{ImplementationId, MAX_VALUE_NODES, Value};
|
|
|
|
|
use runa_rune::{
|
|
|
|
|
ExperimentResult, Invocation, Slice, WireTransition, value_from_rune, value_to_rune,
|
|
|
|
|
};
|
2026-11-02 08:19:04 +00:00
|
|
|
|
2026-11-02 08:31:25 +00:00
|
|
|
pub type RuntimeResult<T> = ExperimentResult<T>;
|
2026-11-02 08:19:04 +00:00
|
|
|
|
|
|
|
|
struct WakeFlag(AtomicBool);
|
|
|
|
|
impl Wake for WakeFlag {
|
|
|
|
|
fn wake(self: Arc<Self>) {
|
|
|
|
|
self.0.store(true, Ordering::SeqCst);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn wake_by_ref(self: &Arc<Self>) {
|
|
|
|
|
self.0.store(true, Ordering::SeqCst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Actor {
|
|
|
|
|
snapshot: ActorSnapshot,
|
|
|
|
|
mailbox: VecDeque<Message>,
|
|
|
|
|
capacity: NonZeroUsize,
|
|
|
|
|
active: Option<Invocation<WireTransition>>,
|
|
|
|
|
wake: Arc<WakeFlag>,
|
2026-11-02 11:35:29 +00:00
|
|
|
paused_from: Option<Status>,
|
2026-11-02 08:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct Runtime {
|
2026-11-02 14:19:25 +00:00
|
|
|
actors: BTreeMap<ActorRef, Actor>,
|
2026-11-02 09:08:49 +00:00
|
|
|
implementations: BTreeMap<ImplementationId, Arc<CompiledArtifact>>,
|
2026-11-02 14:19:25 +00:00
|
|
|
next_mailbox: u64,
|
2026-11-02 08:19:04 +00:00
|
|
|
events: Vec<Event>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Runtime {
|
2026-11-02 09:08:49 +00:00
|
|
|
pub fn publish(
|
|
|
|
|
&mut self,
|
|
|
|
|
artifact: Arc<CompiledArtifact>,
|
|
|
|
|
) -> ExperimentResult<ImplementationId> {
|
|
|
|
|
let id = artifact.implementation_id;
|
|
|
|
|
if let Some(existing) = self.implementations.get(&id) {
|
|
|
|
|
if existing.source_id != artifact.source_id {
|
|
|
|
|
return Err("implementation identity collision".into());
|
|
|
|
|
}
|
|
|
|
|
return Ok(id);
|
|
|
|
|
}
|
|
|
|
|
self.implementations.insert(id, artifact);
|
2026-11-02 08:19:04 +00:00
|
|
|
Ok(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn spawn(
|
|
|
|
|
&mut self,
|
|
|
|
|
implementation: ImplementationId,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
state: Value,
|
2026-11-02 08:19:04 +00:00
|
|
|
capacity: NonZeroUsize,
|
2026-11-02 14:19:25 +00:00
|
|
|
) -> ExperimentResult<ActorRef> {
|
2026-11-02 08:19:04 +00:00
|
|
|
if !self.implementations.contains_key(&implementation) {
|
|
|
|
|
return Err("unknown implementation".into());
|
|
|
|
|
}
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
state.validate()?;
|
2026-11-02 14:19:25 +00:00
|
|
|
self.next_mailbox = self
|
|
|
|
|
.next_mailbox
|
2026-11-02 08:19:04 +00:00
|
|
|
.checked_add(1)
|
2026-11-02 14:19:25 +00:00
|
|
|
.ok_or("mailbox IDs exhausted")?;
|
|
|
|
|
let mut actor_ref = ActorRef::new();
|
|
|
|
|
while self.actors.contains_key(&actor_ref) {
|
|
|
|
|
actor_ref = ActorRef::new();
|
|
|
|
|
}
|
2026-11-02 08:19:04 +00:00
|
|
|
self.actors.insert(
|
2026-11-02 14:19:25 +00:00
|
|
|
actor_ref,
|
2026-11-02 08:19:04 +00:00
|
|
|
Actor {
|
|
|
|
|
snapshot: ActorSnapshot {
|
2026-11-02 14:19:25 +00:00
|
|
|
actor_ref,
|
|
|
|
|
mailbox: MailboxId(self.next_mailbox),
|
2026-11-02 08:19:04 +00:00
|
|
|
implementation,
|
|
|
|
|
incarnation: 1,
|
|
|
|
|
state,
|
|
|
|
|
status: Status::Idle,
|
2026-11-02 12:11:11 +00:00
|
|
|
execution: None,
|
2026-11-02 08:19:04 +00:00
|
|
|
queued: Vec::new(),
|
|
|
|
|
reserved: None,
|
|
|
|
|
},
|
|
|
|
|
mailbox: VecDeque::new(),
|
|
|
|
|
capacity,
|
|
|
|
|
active: None,
|
|
|
|
|
wake: Arc::new(WakeFlag(AtomicBool::new(true))),
|
2026-11-02 11:35:29 +00:00
|
|
|
paused_from: None,
|
2026-11-02 08:19:04 +00:00
|
|
|
},
|
|
|
|
|
);
|
2026-11-02 14:19:25 +00:00
|
|
|
self.events.push(Event::Spawned(actor_ref));
|
|
|
|
|
Ok(actor_ref)
|
2026-11-02 08:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-11-02 14:19:25 +00:00
|
|
|
pub fn send(&mut self, id: ActorRef, message: Message) -> ExperimentResult<()> {
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
message.validate()?;
|
2026-11-02 08:19:04 +00:00
|
|
|
let actor = self.actors.get_mut(&id).ok_or("unknown actor")?;
|
|
|
|
|
if matches!(actor.snapshot.status, Status::Stopped | Status::Failed(_)) {
|
|
|
|
|
return Err("actor is not accepting messages".into());
|
|
|
|
|
}
|
|
|
|
|
if actor.mailbox.len() + usize::from(actor.snapshot.reserved.is_some())
|
|
|
|
|
>= actor.capacity.get()
|
|
|
|
|
{
|
|
|
|
|
return Err("mailbox full".into());
|
|
|
|
|
}
|
|
|
|
|
actor.mailbox.push_back(message);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 14:19:25 +00:00
|
|
|
pub fn inspect(&self, id: ActorRef) -> ExperimentResult<ActorSnapshot> {
|
2026-11-02 08:19:04 +00:00
|
|
|
let actor = self.actors.get(&id).ok_or("unknown actor")?;
|
|
|
|
|
let mut snapshot = actor.snapshot.clone();
|
2026-11-02 12:11:11 +00:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
});
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
snapshot.queued = actor.mailbox.iter().cloned().collect();
|
2026-11-02 08:19:04 +00:00
|
|
|
Ok(snapshot)
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 08:31:25 +00:00
|
|
|
pub fn actors(&self) -> Vec<ActorSnapshot> {
|
|
|
|
|
self.actors
|
|
|
|
|
.values()
|
|
|
|
|
.map(|actor| {
|
|
|
|
|
let mut snapshot = actor.snapshot.clone();
|
2026-11-02 12:11:11 +00:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
});
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
snapshot.queued = actor.mailbox.iter().cloned().collect();
|
2026-11-02 08:31:25 +00:00
|
|
|
snapshot
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 14:19:25 +00:00
|
|
|
pub fn stop(&mut self, id: ActorRef) -> ExperimentResult<()> {
|
2026-11-02 08:19:04 +00:00
|
|
|
let actor = self.actors.get_mut(&id).ok_or("unknown actor")?;
|
|
|
|
|
actor.active = None;
|
2026-11-02 11:35:29 +00:00
|
|
|
actor.paused_from = None;
|
2026-11-02 08:19:04 +00:00
|
|
|
actor.snapshot.status = Status::Stopped;
|
|
|
|
|
self.events.push(Event::Stopped(id));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 11:35:29 +00:00
|
|
|
/// Pause scheduling between worker ticks without discarding an invocation.
|
2026-11-02 14:19:25 +00:00
|
|
|
pub fn pause(&mut self, id: ActorRef) -> ExperimentResult<()> {
|
2026-11-02 11:35:29 +00:00
|
|
|
let actor = self.actors.get_mut(&id).ok_or("unknown actor")?;
|
|
|
|
|
if matches!(
|
|
|
|
|
actor.snapshot.status,
|
|
|
|
|
Status::Paused | Status::Stopped | Status::Failed(_)
|
|
|
|
|
) {
|
|
|
|
|
return Err("actor cannot be paused in its current status".into());
|
|
|
|
|
}
|
|
|
|
|
actor.paused_from = Some(actor.snapshot.status.clone());
|
|
|
|
|
actor.snapshot.status = Status::Paused;
|
|
|
|
|
self.events.push(Event::Paused(id));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Resume from the exact runnable or awaiting state saved by `pause`.
|
2026-11-02 14:19:25 +00:00
|
|
|
pub fn resume(&mut self, id: ActorRef) -> ExperimentResult<()> {
|
2026-11-02 11:35:29 +00:00
|
|
|
let actor = self.actors.get_mut(&id).ok_or("unknown actor")?;
|
|
|
|
|
let status = actor.paused_from.take().ok_or("actor is not paused")?;
|
|
|
|
|
actor.snapshot.status = status;
|
|
|
|
|
self.events.push(Event::Resumed(id));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 12:04:47 +00:00
|
|
|
/// Cancel an active handler and requeue its reserved message for retry.
|
|
|
|
|
///
|
|
|
|
|
/// This does not roll back side effects already performed by native code.
|
2026-11-02 14:19:25 +00:00
|
|
|
pub fn cancel(&mut self, id: ActorRef) -> ExperimentResult<()> {
|
2026-11-02 12:04:47 +00:00
|
|
|
let actor = self.actors.get_mut(&id).ok_or("unknown actor")?;
|
|
|
|
|
if actor.active.is_none() {
|
|
|
|
|
return Err("actor has no active invocation to cancel".into());
|
|
|
|
|
}
|
|
|
|
|
let message = actor
|
|
|
|
|
.snapshot
|
|
|
|
|
.reserved
|
|
|
|
|
.take()
|
|
|
|
|
.ok_or("active invocation has no reserved message")?;
|
|
|
|
|
actor.active = None;
|
|
|
|
|
actor.paused_from = None;
|
|
|
|
|
actor.mailbox.push_front(message);
|
|
|
|
|
actor.snapshot.status = Status::Idle;
|
|
|
|
|
self.events.push(Event::InvocationCancelled(id));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 14:19:25 +00:00
|
|
|
pub fn restart(&mut self, id: ActorRef) -> ExperimentResult<()> {
|
2026-11-02 08:19:04 +00:00
|
|
|
let actor = self.actors.get_mut(&id).ok_or("unknown actor")?;
|
|
|
|
|
if !matches!(actor.snapshot.status, Status::Stopped | Status::Failed(_)) {
|
|
|
|
|
return Err("stop the actor before restarting".into());
|
|
|
|
|
}
|
|
|
|
|
actor.snapshot.incarnation = actor
|
|
|
|
|
.snapshot
|
|
|
|
|
.incarnation
|
|
|
|
|
.checked_add(1)
|
|
|
|
|
.ok_or("incarnation exhausted")?;
|
|
|
|
|
if let Some(message) = actor.snapshot.reserved.take() {
|
|
|
|
|
actor.mailbox.push_front(message);
|
|
|
|
|
}
|
|
|
|
|
actor.snapshot.status = Status::Idle;
|
2026-11-02 11:35:29 +00:00
|
|
|
actor.paused_from = None;
|
2026-11-02 08:19:04 +00:00
|
|
|
self.events.push(Event::Restarted(id));
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 09:18:07 +00:00
|
|
|
/// Install an immutable implementation between handler invocations.
|
|
|
|
|
pub fn install(
|
|
|
|
|
&mut self,
|
2026-11-02 14:19:25 +00:00
|
|
|
id: ActorRef,
|
2026-11-02 09:18:07 +00:00
|
|
|
implementation: ImplementationId,
|
2026-11-02 11:45:02 +00:00
|
|
|
) -> ExperimentResult<()> {
|
|
|
|
|
self.install_with_migration(id, implementation, Ok)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Migrate committed state and install an implementation as one safe-point
|
|
|
|
|
/// operation. A migration error leaves both unchanged.
|
|
|
|
|
pub fn install_with_migration(
|
|
|
|
|
&mut self,
|
2026-11-02 14:19:25 +00:00
|
|
|
id: ActorRef,
|
2026-11-02 11:45:02 +00:00
|
|
|
implementation: ImplementationId,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
migrate: impl FnOnce(Value) -> ExperimentResult<Value>,
|
2026-11-02 09:18:07 +00:00
|
|
|
) -> ExperimentResult<()> {
|
|
|
|
|
if !self.implementations.contains_key(&implementation) {
|
|
|
|
|
return Err("unknown implementation".into());
|
|
|
|
|
}
|
2026-11-02 11:45:02 +00:00
|
|
|
let current = self.actors.get(&id).ok_or("unknown actor")?;
|
|
|
|
|
if current.active.is_some()
|
|
|
|
|
|| current.snapshot.reserved.is_some()
|
|
|
|
|
|| current.snapshot.status != Status::Idle
|
2026-11-02 09:18:07 +00:00
|
|
|
{
|
|
|
|
|
return Err("behavior can only be installed at an idle handler boundary".into());
|
|
|
|
|
}
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let previous_state = current.snapshot.state.clone();
|
|
|
|
|
let migrated_state = migrate(previous_state.clone())
|
|
|
|
|
.map_err(|error| format!("state migration failed: {error}"))?;
|
|
|
|
|
migrated_state.validate()?;
|
2026-11-02 11:45:02 +00:00
|
|
|
|
|
|
|
|
let actor = self.actors.get_mut(&id).expect("actor checked above");
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
actor.snapshot.state = migrated_state.clone();
|
2026-11-02 09:18:07 +00:00
|
|
|
actor.snapshot.implementation = implementation;
|
|
|
|
|
self.events
|
|
|
|
|
.push(Event::BehaviorInstalled(id, implementation));
|
2026-11-02 11:45:02 +00:00
|
|
|
if migrated_state != previous_state {
|
|
|
|
|
self.events
|
|
|
|
|
.push(Event::StateMigrated(id, previous_state, migrated_state));
|
|
|
|
|
}
|
2026-11-02 09:18:07 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 08:19:04 +00:00
|
|
|
pub fn drain_events(&mut self) -> Vec<Event> {
|
|
|
|
|
std::mem::take(&mut self.events)
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 14:19:25 +00:00
|
|
|
fn fail(&mut self, id: &ActorRef, error: String) {
|
|
|
|
|
let actor = self.actors.get_mut(id).expect("scheduled actor exists");
|
2026-11-02 08:19:04 +00:00
|
|
|
actor.active = None;
|
|
|
|
|
actor.snapshot.status = Status::Failed(error.clone());
|
2026-11-02 14:19:25 +00:00
|
|
|
self.events.push(Event::Failed(*id, error));
|
2026-11-02 08:19:04 +00:00
|
|
|
}
|
|
|
|
|
|
2026-11-02 14:19:25 +00:00
|
|
|
fn commit(&mut self, id: &ActorRef, transition: WireTransition) -> ExperimentResult<()> {
|
2026-11-02 08:19:04 +00:00
|
|
|
let (state, effects) = transition;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let state = value_from_rune(&state)?;
|
|
|
|
|
if effects.len() > MAX_VALUE_NODES {
|
|
|
|
|
return Err("transition exceeds effect limit".into());
|
|
|
|
|
}
|
|
|
|
|
let effects = effects
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(target, message)| {
|
|
|
|
|
let Value::ActorRef(target) = value_from_rune(target)? else {
|
|
|
|
|
return Err("effect target must be an actor-reference value".into());
|
|
|
|
|
};
|
|
|
|
|
Ok((target, value_from_rune(message)?))
|
|
|
|
|
})
|
|
|
|
|
.collect::<ExperimentResult<Vec<_>>>()?;
|
2026-11-02 14:19:25 +00:00
|
|
|
let mut counts = BTreeMap::<ActorRef, usize>::new();
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
for (target, _) in &effects {
|
2026-11-02 08:19:04 +00:00
|
|
|
let actor = self
|
|
|
|
|
.actors
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
.get(target)
|
2026-11-02 08:19:04 +00:00
|
|
|
.ok_or("effect targets unknown actor")?;
|
|
|
|
|
if matches!(actor.snapshot.status, Status::Stopped | Status::Failed(_)) {
|
|
|
|
|
return Err("effect targets unavailable actor".into());
|
|
|
|
|
}
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let count = counts.entry(*target).or_default();
|
2026-11-02 08:19:04 +00:00
|
|
|
*count += 1;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let retained = usize::from(target != id && actor.snapshot.reserved.is_some());
|
2026-11-02 08:19:04 +00:00
|
|
|
if actor.mailbox.len() + retained + *count > actor.capacity.get() {
|
|
|
|
|
return Err("effect exceeds mailbox capacity".into());
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-11-02 14:19:25 +00:00
|
|
|
let actor = self.actors.get_mut(id).expect("scheduled actor exists");
|
2026-11-02 08:19:04 +00:00
|
|
|
actor.snapshot.state = state;
|
|
|
|
|
actor.snapshot.reserved = None;
|
|
|
|
|
actor.snapshot.status = Status::Idle;
|
|
|
|
|
actor.active = None;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
for (target, message) in effects {
|
2026-11-02 08:19:04 +00:00
|
|
|
self.actors
|
2026-11-02 14:19:25 +00:00
|
|
|
.get_mut(&target)
|
2026-11-02 08:19:04 +00:00
|
|
|
.expect("validated target")
|
|
|
|
|
.mailbox
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
.push_back(message);
|
2026-11-02 08:19:04 +00:00
|
|
|
}
|
2026-11-02 14:19:25 +00:00
|
|
|
self.events.push(Event::Committed(*id));
|
2026-11-02 08:19:04 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Give each runnable actor one bounded slice. Awaiting actors require a wake.
|
|
|
|
|
pub fn tick(&mut self, budget: NonZeroUsize) {
|
|
|
|
|
let ids: Vec<_> = self.actors.keys().copied().collect();
|
|
|
|
|
for id in ids {
|
|
|
|
|
let actor = self.actors.get_mut(&id).expect("scheduled actor exists");
|
2026-11-02 11:35:29 +00:00
|
|
|
if matches!(
|
|
|
|
|
actor.snapshot.status,
|
|
|
|
|
Status::Paused | Status::Stopped | Status::Failed(_)
|
|
|
|
|
) {
|
2026-11-02 08:19:04 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if actor.active.is_none() {
|
|
|
|
|
let Some(message) = actor.mailbox.pop_front() else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
actor.snapshot.reserved = Some(message.clone());
|
2026-11-02 09:08:49 +00:00
|
|
|
let program = self.implementations[&actor.snapshot.implementation].program();
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let invocation = (|| -> ExperimentResult<_> {
|
|
|
|
|
let state = value_to_rune(&actor.snapshot.state)?;
|
|
|
|
|
let message = value_to_rune(&message)?;
|
|
|
|
|
Ok(Invocation::start(program.vm(), "handle", (state, message))?)
|
|
|
|
|
})();
|
|
|
|
|
match invocation {
|
2026-11-02 08:19:04 +00:00
|
|
|
Ok(invocation) => actor.active = Some(invocation),
|
|
|
|
|
Err(error) => {
|
2026-11-02 14:19:25 +00:00
|
|
|
self.fail(&id, error.to_string());
|
2026-11-02 08:19:04 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
actor.snapshot.status = Status::Running;
|
|
|
|
|
}
|
|
|
|
|
if actor.snapshot.status == Status::Awaiting && !actor.wake.0.load(Ordering::SeqCst) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
actor.wake.0.store(false, Ordering::SeqCst);
|
|
|
|
|
let waker = Waker::from(Arc::clone(&actor.wake));
|
|
|
|
|
let mut cx = Context::from_waker(&waker);
|
|
|
|
|
let outcome = actor
|
|
|
|
|
.active
|
|
|
|
|
.as_mut()
|
|
|
|
|
.expect("active invocation")
|
|
|
|
|
.poll_slice(&mut cx, budget);
|
|
|
|
|
match outcome {
|
|
|
|
|
Slice::Completed(transition) => {
|
2026-11-02 14:19:25 +00:00
|
|
|
if let Err(error) = self.commit(&id, transition) {
|
|
|
|
|
self.fail(&id, error.to_string());
|
2026-11-02 08:19:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Slice::Awaiting => actor.snapshot.status = Status::Awaiting,
|
|
|
|
|
Slice::BudgetExhausted => actor.snapshot.status = Status::Running,
|
2026-11-02 14:19:25 +00:00
|
|
|
Slice::Failed(error) => self.fail(&id, error.to_string()),
|
2026-11-02 08:19:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
|
|
|
|
|
fn test_message(kind: i64, value: i64, target: Option<runa_core::ActorRef>) -> Value {
|
|
|
|
|
Value::Record(std::collections::BTreeMap::from([
|
|
|
|
|
("kind".to_owned(), Value::Integer(kind)),
|
|
|
|
|
("value".to_owned(), Value::Integer(value)),
|
|
|
|
|
(
|
|
|
|
|
"target".to_owned(),
|
|
|
|
|
target.map_or(Value::Null, Value::ActorRef),
|
|
|
|
|
),
|
|
|
|
|
]))
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 09:08:49 +00:00
|
|
|
use runa_code::CodeStore;
|
2026-11-02 08:19:04 +00:00
|
|
|
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
#[test]
|
|
|
|
|
fn nested_state_and_messages_are_private_and_sends_copy_owned_values() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let receiver_code = publish(
|
|
|
|
|
&mut runtime,
|
|
|
|
|
"pub fn handle(state, message) { message.items.push(9); (message, []) }",
|
|
|
|
|
)?;
|
|
|
|
|
let sender_code = publish(
|
|
|
|
|
&mut runtime,
|
|
|
|
|
"pub fn handle(state, message) { state.items.push(message.text); (state, [(message.target, state)]) }",
|
|
|
|
|
)?;
|
|
|
|
|
let initial = Value::Record(BTreeMap::from([(
|
|
|
|
|
"items".to_owned(),
|
|
|
|
|
Value::List(vec![Value::Bool(true), Value::Null]),
|
|
|
|
|
)]));
|
|
|
|
|
let receiver = runtime.spawn(receiver_code, Value::Null, capacity(2))?;
|
|
|
|
|
let sender = runtime.spawn(sender_code, initial.clone(), capacity(2))?;
|
|
|
|
|
let message = Value::Record(BTreeMap::from([
|
|
|
|
|
("target".to_owned(), Value::ActorRef(receiver)),
|
|
|
|
|
("text".to_owned(), Value::String("hello world".to_owned())),
|
|
|
|
|
]));
|
|
|
|
|
runtime.send(sender, message.clone())?;
|
|
|
|
|
runtime.tick(capacity(1));
|
|
|
|
|
assert_eq!(runtime.inspect(sender)?.state, initial);
|
|
|
|
|
assert_eq!(runtime.inspect(sender)?.reserved, Some(message));
|
|
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(32));
|
|
|
|
|
}
|
|
|
|
|
let sender_state = Value::Record(BTreeMap::from([(
|
|
|
|
|
"items".to_owned(),
|
|
|
|
|
Value::List(vec![
|
|
|
|
|
Value::Bool(true),
|
|
|
|
|
Value::Null,
|
|
|
|
|
Value::String("hello world".to_owned()),
|
|
|
|
|
]),
|
|
|
|
|
)]));
|
|
|
|
|
let receiver_state = Value::Record(BTreeMap::from([(
|
|
|
|
|
"items".to_owned(),
|
|
|
|
|
Value::List(vec![
|
|
|
|
|
Value::Bool(true),
|
|
|
|
|
Value::Null,
|
|
|
|
|
Value::String("hello world".to_owned()),
|
|
|
|
|
Value::Integer(9),
|
|
|
|
|
]),
|
|
|
|
|
)]));
|
|
|
|
|
assert_eq!(runtime.inspect(sender)?.state, sender_state);
|
|
|
|
|
assert_eq!(runtime.inspect(receiver)?.state, receiver_state);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn mutated_drafts_and_invalid_structured_effects_never_partially_commit() -> ExperimentResult<()>
|
|
|
|
|
{
|
|
|
|
|
for source in [
|
|
|
|
|
"pub fn handle(state, message) { state.items.push(2); panic(\"failed\"); (state, []) }",
|
|
|
|
|
"pub fn handle(state, message) { state.items.push(2); (state, [(message, state), (\"not a reference\", state)]) }",
|
|
|
|
|
"pub fn handle(state, message) { state.items.push(2); (state, [(message, state), (message, 1.5)]) }",
|
|
|
|
|
"pub fn handle(state, message) { state.items.push(2); (Some(state), [(message, state)]) }",
|
|
|
|
|
"pub fn handle(state, message) { state.items.push(state); (state, []) }",
|
|
|
|
|
] {
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let code = publish(&mut runtime, source)?;
|
|
|
|
|
let initial = Value::Record(BTreeMap::from([(
|
|
|
|
|
"items".to_owned(),
|
|
|
|
|
Value::List(vec![Value::Integer(1)]),
|
|
|
|
|
)]));
|
|
|
|
|
let actor = runtime.spawn(code, initial.clone(), capacity(4))?;
|
|
|
|
|
runtime.send(actor, Value::ActorRef(actor))?;
|
|
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(32));
|
|
|
|
|
}
|
|
|
|
|
let snapshot = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(snapshot.state, initial, "{source}");
|
|
|
|
|
assert_eq!(snapshot.reserved, Some(Value::ActorRef(actor)), "{source}");
|
|
|
|
|
assert!(snapshot.queued.is_empty(), "{source}");
|
|
|
|
|
assert!(matches!(snapshot.status, Status::Failed(_)), "{source}");
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn invalid_input_and_migration_values_leave_the_image_unchanged() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let code = publish(
|
|
|
|
|
&mut runtime,
|
|
|
|
|
"pub fn handle(state, message) { (message, []) }",
|
|
|
|
|
)?;
|
|
|
|
|
let invalid = Value::List(vec![Value::Null; MAX_VALUE_NODES]);
|
|
|
|
|
assert!(runtime.spawn(code, invalid.clone(), capacity(2)).is_err());
|
|
|
|
|
assert!(runtime.actors().is_empty());
|
|
|
|
|
let actor = runtime.spawn(code, Value::Bool(true), capacity(2))?;
|
|
|
|
|
assert!(runtime.send(actor, invalid.clone()).is_err());
|
|
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.install_with_migration(actor, code, |_| Ok(invalid))
|
|
|
|
|
.is_err()
|
|
|
|
|
);
|
|
|
|
|
let snapshot = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(snapshot.state, Value::Bool(true));
|
|
|
|
|
assert!(snapshot.queued.is_empty());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 08:19:04 +00:00
|
|
|
fn capacity(n: usize) -> NonZeroUsize {
|
|
|
|
|
NonZeroUsize::new(n).unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 09:08:49 +00:00
|
|
|
fn publish(runtime: &mut Runtime, source: &str) -> ExperimentResult<ImplementationId> {
|
|
|
|
|
runtime.publish(CodeStore::default().compile(source)?)
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 08:19:04 +00:00
|
|
|
#[test]
|
|
|
|
|
fn two_actors_isolation_and_effects() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
2026-11-02 09:08:49 +00:00
|
|
|
let code = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { if message.kind == 1 { (state + 1, [(message.target, #{kind: 0, value: 5, target: ()})]) } else { (state + message.value, []) } }",
|
2026-11-02 08:19:04 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let first = runtime.spawn(code, Value::Integer(0), capacity(4))?;
|
|
|
|
|
let second = runtime.spawn(code, Value::Integer(10), capacity(4))?;
|
|
|
|
|
runtime.send(first, test_message(1, 0, Some(second)))?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(10));
|
|
|
|
|
}
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(runtime.inspect(first)?.state, Value::Integer(1));
|
|
|
|
|
assert_eq!(runtime.inspect(second)?.state, Value::Integer(15));
|
2026-11-02 08:19:04 +00:00
|
|
|
assert!(runtime.inspect(first)?.reserved.is_none());
|
|
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.drain_events()
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|e| matches!(e, Event::Committed(id) if *id == second))
|
|
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn runaway_does_not_starve_healthy_actor() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let runaway = publish(&mut runtime, "pub fn handle(state, message) { loop {} }")?;
|
2026-11-02 09:08:49 +00:00
|
|
|
let healthy = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state + message.value, []) }",
|
2026-11-02 14:19:25 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let first = runtime.spawn(runaway, Value::Integer(0), capacity(1))?;
|
|
|
|
|
let second = runtime.spawn(healthy, Value::Integer(0), capacity(2))?;
|
|
|
|
|
runtime.send(first, test_message(0, 1, None))?;
|
|
|
|
|
runtime.send(second, test_message(0, 42, None))?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(runtime.inspect(first)?.status, Status::Running);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(runtime.inspect(first)?.state, Value::Integer(0));
|
|
|
|
|
assert_eq!(runtime.inspect(second)?.state, Value::Integer(42));
|
|
|
|
|
assert!(runtime.send(first, test_message(0, 2, None)).is_err());
|
2026-11-02 08:19:04 +00:00
|
|
|
runtime.stop(first)?;
|
|
|
|
|
assert_eq!(runtime.inspect(first)?.status, Status::Stopped);
|
|
|
|
|
runtime.restart(first)?;
|
|
|
|
|
assert_eq!(runtime.inspect(first)?.incarnation, 2);
|
|
|
|
|
assert_eq!(runtime.inspect(first)?.queued.len(), 1);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 12:11:11 +00:00
|
|
|
#[test]
|
|
|
|
|
fn execution_snapshot_reports_live_progress_and_is_stable_while_paused() -> ExperimentResult<()>
|
|
|
|
|
{
|
|
|
|
|
let mut runtime = Runtime::default();
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let implementation = publish(&mut runtime, "pub fn handle(state, message) { loop {} }")?;
|
|
|
|
|
let actor = runtime.spawn(implementation, Value::Integer(0), capacity(2))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 1, None))?;
|
2026-11-02 12:11:11 +00:00
|
|
|
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);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(cancelled.queued, vec![test_message(0, 1, None)]);
|
2026-11-02 12:11:11 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 08:19:04 +00:00
|
|
|
#[test]
|
|
|
|
|
fn invalid_effect_does_not_commit_state_or_partial_sends() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
2026-11-02 09:08:49 +00:00
|
|
|
let code = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (99, [(message.target, #{kind: 0, value: 1, target: ()}), (\"invalid\", #{kind: 0, value: 2, target: ()})]) }",
|
2026-11-02 08:19:04 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(code, Value::Integer(5), capacity(4))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 0, Some(actor)))?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(10));
|
|
|
|
|
}
|
|
|
|
|
let snapshot = runtime.inspect(actor)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(snapshot.state, Value::Integer(5));
|
2026-11-02 08:19:04 +00:00
|
|
|
assert!(matches!(snapshot.status, Status::Failed(_)));
|
|
|
|
|
assert!(snapshot.queued.is_empty());
|
|
|
|
|
assert!(snapshot.reserved.is_some());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn awaiting_retains_message_and_state_until_woken() -> ExperimentResult<()> {
|
|
|
|
|
use std::future::Future;
|
|
|
|
|
use std::pin::Pin;
|
|
|
|
|
use std::sync::Mutex;
|
|
|
|
|
use std::task::Poll;
|
|
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
struct Gate {
|
|
|
|
|
ready: AtomicBool,
|
|
|
|
|
dropped: AtomicBool,
|
|
|
|
|
polls: std::sync::atomic::AtomicUsize,
|
|
|
|
|
waker: Mutex<Option<Waker>>,
|
|
|
|
|
}
|
|
|
|
|
struct Wait(Arc<Gate>);
|
|
|
|
|
impl Future for Wait {
|
|
|
|
|
type Output = i64;
|
|
|
|
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<i64> {
|
|
|
|
|
self.0.polls.fetch_add(1, Ordering::SeqCst);
|
|
|
|
|
if self.0.ready.load(Ordering::SeqCst) {
|
|
|
|
|
Poll::Ready(7)
|
|
|
|
|
} else {
|
|
|
|
|
*self.0.waker.lock().unwrap() = Some(cx.waker().clone());
|
|
|
|
|
Poll::Pending
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl Drop for Wait {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
self.0.dropped.store(true, Ordering::SeqCst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let gate = Arc::new(Gate::default());
|
|
|
|
|
let native_gate = Arc::clone(&gate);
|
|
|
|
|
let mut module = rune::Module::new();
|
|
|
|
|
module
|
|
|
|
|
.function("wait", move || Wait(Arc::clone(&native_gate)))
|
|
|
|
|
.build()?;
|
|
|
|
|
let mut runtime = Runtime::default();
|
2026-11-02 09:08:49 +00:00
|
|
|
let code = runtime.publish(
|
|
|
|
|
CodeStore::default().compile_with_module(
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub async fn handle(state, message) { if message.kind == 1 { let x = wait().await; (state + x, []) } else { (state + message.value, []) } }",
|
2026-11-02 09:08:49 +00:00
|
|
|
"test-gate:v1",
|
|
|
|
|
module,
|
|
|
|
|
)?,
|
|
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(code, Value::Integer(10), capacity(2))?;
|
|
|
|
|
runtime.send(actor, test_message(1, 0, None))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 3, None))?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
let pending = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(pending.status, Status::Awaiting);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(pending.state, Value::Integer(10));
|
2026-11-02 08:19:04 +00:00
|
|
|
assert_eq!(pending.queued.len(), 1);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(pending.reserved, Some(test_message(1, 0, None)));
|
2026-11-02 08:19:04 +00:00
|
|
|
let polls = gate.polls.load(Ordering::SeqCst);
|
|
|
|
|
for _ in 0..10 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(gate.polls.load(Ordering::SeqCst), polls);
|
2026-11-02 11:35:29 +00:00
|
|
|
runtime.pause(actor)?;
|
|
|
|
|
assert_eq!(runtime.inspect(actor)?.status, Status::Paused);
|
2026-11-02 08:19:04 +00:00
|
|
|
gate.ready.store(true, Ordering::SeqCst);
|
|
|
|
|
gate.waker
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.take()
|
|
|
|
|
.expect("registered wake")
|
|
|
|
|
.wake();
|
2026-11-02 11:35:29 +00:00
|
|
|
for _ in 0..10 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(gate.polls.load(Ordering::SeqCst), polls);
|
|
|
|
|
assert_eq!(runtime.inspect(actor)?.status, Status::Paused);
|
|
|
|
|
runtime.resume(actor)?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
let completed = runtime.inspect(actor)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(completed.state, Value::Integer(20));
|
2026-11-02 08:19:04 +00:00
|
|
|
assert_eq!(completed.status, Status::Idle);
|
|
|
|
|
assert!(completed.reserved.is_none());
|
|
|
|
|
assert!(completed.queued.is_empty());
|
|
|
|
|
|
|
|
|
|
gate.ready.store(false, Ordering::SeqCst);
|
|
|
|
|
gate.dropped.store(false, Ordering::SeqCst);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
runtime.send(actor, test_message(1, 0, None))?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
assert_eq!(runtime.inspect(actor)?.status, Status::Awaiting);
|
2026-11-02 12:04:47 +00:00
|
|
|
runtime.cancel(actor)?;
|
2026-11-02 08:19:04 +00:00
|
|
|
assert!(gate.dropped.load(Ordering::SeqCst));
|
2026-11-02 12:04:47 +00:00
|
|
|
let cancelled = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(cancelled.mailbox, pending.mailbox);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(cancelled.state, Value::Integer(20));
|
2026-11-02 12:04:47 +00:00
|
|
|
assert_eq!(cancelled.status, Status::Idle);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(cancelled.queued, vec![test_message(1, 0, None)]);
|
2026-11-02 12:04:47 +00:00
|
|
|
assert!(cancelled.reserved.is_none());
|
2026-11-02 08:19:04 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 11:35:29 +00:00
|
|
|
#[test]
|
|
|
|
|
fn pause_preserves_active_invocation_and_resume_continues_it() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state + message.value, []) }",
|
2026-11-02 14:19:25 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(implementation, Value::Integer(10), capacity(2))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 7, None))?;
|
2026-11-02 11:35:29 +00:00
|
|
|
runtime.tick(capacity(1));
|
|
|
|
|
let before_pause = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(before_pause.status, Status::Running);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(before_pause.state, Value::Integer(10));
|
2026-11-02 11:35:29 +00:00
|
|
|
assert!(before_pause.reserved.is_some());
|
|
|
|
|
|
|
|
|
|
runtime.pause(actor)?;
|
|
|
|
|
assert!(runtime.pause(actor).is_err());
|
|
|
|
|
for _ in 0..10 {
|
|
|
|
|
runtime.tick(capacity(10));
|
|
|
|
|
}
|
|
|
|
|
let paused = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(paused.status, Status::Paused);
|
|
|
|
|
assert_eq!(paused.state, before_pause.state);
|
|
|
|
|
assert_eq!(paused.reserved, before_pause.reserved);
|
|
|
|
|
|
|
|
|
|
runtime.resume(actor)?;
|
|
|
|
|
assert!(runtime.resume(actor).is_err());
|
|
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(10));
|
|
|
|
|
}
|
|
|
|
|
let completed = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(completed.status, Status::Idle);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(completed.state, Value::Integer(17));
|
2026-11-02 11:35:29 +00:00
|
|
|
assert!(completed.reserved.is_none());
|
|
|
|
|
let events = runtime.drain_events();
|
|
|
|
|
assert!(
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| matches!(event, Event::Paused(id) if *id == actor))
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| matches!(event, Event::Resumed(id) if *id == actor))
|
|
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 08:19:04 +00:00
|
|
|
#[test]
|
|
|
|
|
fn mailbox_capacity_and_runtime_failures_are_explicit() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
2026-11-02 09:08:49 +00:00
|
|
|
let code = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state / message.value, []) }",
|
2026-11-02 14:19:25 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(code, Value::Integer(5), capacity(1))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 0, None))?;
|
|
|
|
|
assert!(runtime.send(actor, test_message(0, 1, None)).is_err());
|
2026-11-02 14:19:25 +00:00
|
|
|
assert!(runtime.inspect(ActorRef::new()).is_err());
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
assert!(matches!(runtime.inspect(actor)?.status, Status::Failed(_)));
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(runtime.inspect(actor)?.state, Value::Integer(5));
|
2026-11-02 08:19:04 +00:00
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.drain_events()
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|e| matches!(e, Event::Failed(id, _) if *id == actor))
|
|
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn aggregate_self_sends_respect_capacity_without_partial_commit() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
2026-11-02 09:08:49 +00:00
|
|
|
let code = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (99, [(message.target, #{kind: 0, value: 1, target: ()}), (message.target, #{kind: 0, value: 2, target: ()})]) }",
|
2026-11-02 08:19:04 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(code, Value::Integer(5), capacity(1))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 0, Some(actor)))?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
let snapshot = runtime.inspect(actor)?;
|
|
|
|
|
assert!(matches!(snapshot.status, Status::Failed(_)));
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(snapshot.state, Value::Integer(5));
|
2026-11-02 08:19:04 +00:00
|
|
|
assert!(snapshot.queued.is_empty());
|
|
|
|
|
assert!(snapshot.reserved.is_some());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn invalid_transition_shape_fails_without_committing() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let code = publish(&mut runtime, "pub fn handle(state, message) { 99 }")?;
|
|
|
|
|
let actor = runtime.spawn(code, Value::Integer(5), capacity(1))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 1, None))?;
|
2026-11-02 08:19:04 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
let snapshot = runtime.inspect(actor)?;
|
|
|
|
|
assert!(matches!(snapshot.status, Status::Failed(_)));
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(snapshot.state, Value::Integer(5));
|
2026-11-02 08:19:04 +00:00
|
|
|
assert!(snapshot.reserved.is_some());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-11-02 08:27:29 +00:00
|
|
|
|
2026-11-02 09:18:07 +00:00
|
|
|
#[test]
|
|
|
|
|
fn behavior_installation_waits_for_handler_boundary() -> ExperimentResult<()> {
|
|
|
|
|
let mut code = CodeStore::default();
|
2026-11-02 14:19:25 +00:00
|
|
|
let first =
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
code.compile("pub fn handle(state, message) { (state + message.value, []) }")?;
|
|
|
|
|
let second =
|
|
|
|
|
code.compile("pub fn handle(state, message) { (state + message.value * 10, []) }")?;
|
2026-11-02 09:18:07 +00:00
|
|
|
let first_id = first.implementation_id;
|
|
|
|
|
let second_id = second.implementation_id;
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
runtime.publish(first)?;
|
|
|
|
|
runtime.publish(second)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(first_id, Value::Integer(0), capacity(2))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 1, None))?;
|
2026-11-02 09:18:07 +00:00
|
|
|
runtime.tick(capacity(1));
|
|
|
|
|
let active = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(active.implementation, first_id);
|
|
|
|
|
assert!(runtime.install(actor, second_id).is_err());
|
|
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(10));
|
|
|
|
|
}
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(runtime.inspect(actor)?.state, Value::Integer(1));
|
|
|
|
|
runtime.send(actor, test_message(0, 2, None))?;
|
2026-11-02 09:18:07 +00:00
|
|
|
runtime.install(actor, second_id)?;
|
|
|
|
|
assert_eq!(runtime.inspect(actor)?.implementation, second_id);
|
|
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(10));
|
|
|
|
|
}
|
|
|
|
|
let updated = runtime.inspect(actor)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(updated.state, Value::Integer(21));
|
2026-11-02 09:18:07 +00:00
|
|
|
assert_eq!(updated.implementation, second_id);
|
|
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.drain_events()
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| matches!(event, Event::BehaviorInstalled(id, version) if *id == actor && *version == second_id))
|
|
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 11:45:02 +00:00
|
|
|
#[test]
|
|
|
|
|
fn behavior_installation_migrates_state_atomically() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let old_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state + message.value, []) }",
|
2026-11-02 11:45:02 +00:00
|
|
|
)?;
|
|
|
|
|
let new_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state * message.value, []) }",
|
2026-11-02 11:45:02 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(old_implementation, Value::Integer(7), capacity(2))?;
|
2026-11-02 11:45:02 +00:00
|
|
|
|
|
|
|
|
runtime.install_with_migration(actor, new_implementation, |state| {
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let Value::Integer(state) = state else {
|
|
|
|
|
return Err("expected integer".into());
|
|
|
|
|
};
|
|
|
|
|
Ok(Value::Integer(
|
|
|
|
|
state.checked_mul(10).ok_or("state migration overflow")?,
|
|
|
|
|
))
|
2026-11-02 11:45:02 +00:00
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let migrated = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(migrated.implementation, new_implementation);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(migrated.state, Value::Integer(70));
|
2026-11-02 11:45:02 +00:00
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.drain_events()
|
|
|
|
|
.iter()
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
.any(|event| matches!(event, Event::StateMigrated(id, Value::Integer(7), Value::Integer(70)) if *id == actor))
|
2026-11-02 11:45:02 +00:00
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 12:04:47 +00:00
|
|
|
#[test]
|
|
|
|
|
fn cancelled_input_retries_first_with_new_behavior() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let old_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state + message.value, []) }",
|
2026-11-02 12:04:47 +00:00
|
|
|
)?;
|
|
|
|
|
let new_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state * 10 + message.value, []) }",
|
2026-11-02 14:19:25 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(old_implementation, Value::Integer(0), capacity(4))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 1, None))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 2, None))?;
|
2026-11-02 12:04:47 +00:00
|
|
|
runtime.tick(capacity(1));
|
|
|
|
|
|
|
|
|
|
assert_eq!(runtime.inspect(actor)?.status, Status::Running);
|
|
|
|
|
runtime.cancel(actor)?;
|
|
|
|
|
let cancelled = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(cancelled.status, Status::Idle);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(cancelled.state, Value::Integer(0));
|
2026-11-02 12:04:47 +00:00
|
|
|
assert_eq!(
|
|
|
|
|
cancelled.queued,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
vec![test_message(0, 1, None), test_message(0, 2, None)]
|
2026-11-02 12:04:47 +00:00
|
|
|
);
|
|
|
|
|
assert!(cancelled.reserved.is_none());
|
|
|
|
|
|
|
|
|
|
runtime.install(actor, new_implementation)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
runtime.send(actor, test_message(0, 3, None))?;
|
2026-11-02 12:04:47 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(10));
|
|
|
|
|
}
|
|
|
|
|
let completed = runtime.inspect(actor)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(completed.state, Value::Integer(123));
|
2026-11-02 12:04:47 +00:00
|
|
|
assert_eq!(completed.implementation, new_implementation);
|
|
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.drain_events()
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| matches!(event, Event::InvocationCancelled(id) if *id == actor))
|
|
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 11:45:02 +00:00
|
|
|
#[test]
|
|
|
|
|
fn failed_state_migration_preserves_actor_state_and_implementation() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let old_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state + message.value, []) }",
|
2026-11-02 11:45:02 +00:00
|
|
|
)?;
|
|
|
|
|
let new_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state * message.value, []) }",
|
2026-11-02 11:45:02 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(old_implementation, Value::Integer(7), capacity(2))?;
|
2026-11-02 11:45:02 +00:00
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.install_with_migration(actor, new_implementation, |_| {
|
|
|
|
|
Err("unsupported state version".into())
|
|
|
|
|
})
|
|
|
|
|
.is_err()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let unchanged = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(unchanged.implementation, old_implementation);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(unchanged.state, Value::Integer(7));
|
2026-11-02 11:45:02 +00:00
|
|
|
assert!(
|
|
|
|
|
!runtime
|
|
|
|
|
.drain_events()
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| matches!(event, Event::BehaviorInstalled(id, _) if *id == actor))
|
|
|
|
|
);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn migration_is_not_run_outside_an_idle_handler_boundary() -> ExperimentResult<()> {
|
|
|
|
|
use std::cell::Cell;
|
|
|
|
|
|
|
|
|
|
let mut runtime = Runtime::default();
|
|
|
|
|
let old_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state + message.value, []) }",
|
2026-11-02 11:45:02 +00:00
|
|
|
)?;
|
|
|
|
|
let new_implementation = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state * message.value, []) }",
|
2026-11-02 14:19:25 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(old_implementation, Value::Integer(7), capacity(2))?;
|
|
|
|
|
runtime.send(actor, test_message(0, 1, None))?;
|
2026-11-02 11:45:02 +00:00
|
|
|
runtime.tick(capacity(1));
|
|
|
|
|
let called = Cell::new(false);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.install_with_migration(actor, new_implementation, |state| {
|
|
|
|
|
called.set(true);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let Value::Integer(state) = state else {
|
|
|
|
|
return Err("expected integer".into());
|
|
|
|
|
};
|
|
|
|
|
Ok(Value::Integer(state + 100))
|
2026-11-02 11:45:02 +00:00
|
|
|
})
|
|
|
|
|
.is_err()
|
|
|
|
|
);
|
|
|
|
|
assert!(!called.get());
|
|
|
|
|
assert_eq!(runtime.inspect(actor)?.implementation, old_implementation);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-11-02 08:27:29 +00:00
|
|
|
#[test]
|
|
|
|
|
fn restart_retries_failed_reservation_from_last_committed_state() -> ExperimentResult<()> {
|
|
|
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
|
|
|
|
|
|
let fail_once = Arc::new(AtomicBool::new(true));
|
|
|
|
|
let native_fail_once = Arc::clone(&fail_once);
|
|
|
|
|
let mut module = rune::Module::new();
|
|
|
|
|
module
|
|
|
|
|
.function("fail_once", move || {
|
|
|
|
|
native_fail_once.swap(false, Ordering::SeqCst)
|
|
|
|
|
})
|
|
|
|
|
.build()?;
|
|
|
|
|
let mut runtime = Runtime::default();
|
2026-11-02 09:08:49 +00:00
|
|
|
let code = runtime.publish(
|
|
|
|
|
CodeStore::default().compile_with_module(
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"fn check() { if fail_once() { 1 / 0 } else { 0 } } pub fn handle(state, message) { let _ = check(); (state + message.value, []) }",
|
2026-11-02 09:08:49 +00:00
|
|
|
"test-fail-once:v1",
|
|
|
|
|
module,
|
|
|
|
|
)?,
|
|
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(code, Value::Integer(10), capacity(3))?;
|
|
|
|
|
let first = test_message(1, 2, None);
|
|
|
|
|
let second = test_message(1, 3, None);
|
|
|
|
|
runtime.send(actor, first.clone())?;
|
|
|
|
|
runtime.send(actor, second.clone())?;
|
2026-11-02 08:27:29 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let failed = runtime.inspect(actor)?;
|
|
|
|
|
assert!(matches!(failed.status, Status::Failed(_)));
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(failed.state, Value::Integer(10));
|
|
|
|
|
assert_eq!(failed.reserved, Some(first.clone()));
|
|
|
|
|
assert_eq!(failed.queued, vec![second.clone()]);
|
|
|
|
|
assert!(runtime.send(actor, test_message(1, 4, None)).is_err());
|
2026-11-02 08:27:29 +00:00
|
|
|
assert!(
|
|
|
|
|
runtime
|
|
|
|
|
.drain_events()
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|event| matches!(event, Event::Failed(id, _) if *id == actor))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
runtime.restart(actor)?;
|
|
|
|
|
let restarted = runtime.inspect(actor)?;
|
2026-11-02 14:19:25 +00:00
|
|
|
assert_eq!(restarted.actor_ref, failed.actor_ref);
|
2026-11-02 08:27:29 +00:00
|
|
|
assert_eq!(restarted.mailbox, failed.mailbox);
|
|
|
|
|
assert_eq!(restarted.implementation, failed.implementation);
|
|
|
|
|
assert_eq!(restarted.incarnation, failed.incarnation + 1);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(restarted.state, Value::Integer(10));
|
|
|
|
|
assert_eq!(restarted.queued, vec![first.clone(), second.clone()]);
|
2026-11-02 08:27:29 +00:00
|
|
|
assert_eq!(restarted.reserved, None);
|
|
|
|
|
|
|
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
let completed = runtime.inspect(actor)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(completed.state, Value::Integer(15), "{completed:?}");
|
2026-11-02 08:27:29 +00:00
|
|
|
assert_eq!(completed.status, Status::Idle);
|
|
|
|
|
assert_eq!(completed.queued, Vec::<Message>::new());
|
|
|
|
|
assert_eq!(completed.reserved, None);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn stop_and_restart_preserve_reserved_and_queued_fifo_order() -> ExperimentResult<()> {
|
|
|
|
|
let mut runtime = Runtime::default();
|
2026-11-02 09:08:49 +00:00
|
|
|
let code = publish(
|
|
|
|
|
&mut runtime,
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
"pub fn handle(state, message) { (state * 10 + message.value, []) }",
|
2026-11-02 09:08:49 +00:00
|
|
|
)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
let actor = runtime.spawn(code, Value::Integer(0), capacity(3))?;
|
|
|
|
|
let first = test_message(0, 1, None);
|
|
|
|
|
let second = test_message(0, 2, None);
|
|
|
|
|
let third = test_message(0, 3, None);
|
|
|
|
|
runtime.send(actor, first.clone())?;
|
|
|
|
|
runtime.send(actor, second.clone())?;
|
|
|
|
|
runtime.send(actor, third.clone())?;
|
2026-11-02 08:27:29 +00:00
|
|
|
|
|
|
|
|
runtime.tick(capacity(1));
|
|
|
|
|
let running = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(running.status, Status::Running);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(running.state, Value::Integer(0));
|
|
|
|
|
assert_eq!(running.reserved, Some(first.clone()));
|
|
|
|
|
assert_eq!(running.queued, vec![second.clone(), third.clone()]);
|
2026-11-02 08:27:29 +00:00
|
|
|
|
|
|
|
|
runtime.stop(actor)?;
|
|
|
|
|
let stopped = runtime.inspect(actor)?;
|
|
|
|
|
assert_eq!(stopped.status, Status::Stopped);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(stopped.state, Value::Integer(0));
|
|
|
|
|
assert_eq!(stopped.reserved, Some(first.clone()));
|
|
|
|
|
assert_eq!(stopped.queued, vec![second.clone(), third.clone()]);
|
|
|
|
|
assert!(runtime.send(actor, test_message(0, 4, None)).is_err());
|
2026-11-02 08:27:29 +00:00
|
|
|
|
|
|
|
|
runtime.restart(actor)?;
|
|
|
|
|
let restarted = runtime.inspect(actor)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(restarted.state, Value::Integer(0));
|
2026-11-02 08:27:29 +00:00
|
|
|
assert_eq!(restarted.reserved, None);
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(
|
|
|
|
|
restarted.queued,
|
|
|
|
|
vec![first.clone(), second.clone(), third.clone()]
|
|
|
|
|
);
|
2026-11-02 08:27:29 +00:00
|
|
|
for _ in 0..100 {
|
|
|
|
|
runtime.tick(capacity(5));
|
|
|
|
|
}
|
|
|
|
|
let completed = runtime.inspect(actor)?;
|
feat!: add structured actor state and messages
Introduce owned portable values with distinct actor references, bounded Rune conversion, JSON REPL literals, and nested reference completion. Preserve isolation and atomic transitions across state, messages, effects, migration, inspection, and TCP clients.
BREAKING CHANGE: protocol v3 replaces integer state and kind/value/target_ref messages with tagged values. Rune handlers now take (state, message) and return (new_state, [(actor_reference, message)]). The REPL send command accepts one JSON message.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-02 15:56:03 +00:00
|
|
|
assert_eq!(completed.state, Value::Integer(123));
|
2026-11-02 08:27:29 +00:00
|
|
|
assert_eq!(completed.status, Status::Idle);
|
|
|
|
|
assert_eq!(completed.queued, Vec::<Message>::new());
|
|
|
|
|
assert!(completed.reserved.is_none());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-11-02 08:19:04 +00:00
|
|
|
}
|