bin-runner #36
1 changed files with 176 additions and 4 deletions
|
|
@ -1300,9 +1300,18 @@ fn wasm_actor_works_on_multi_worker_runtime() {
|
|||
|
||||
// Use run() to drive the runtime on background threads
|
||||
let handle = rt.run().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
|
||||
let received = inbox.try_recv().expect("wasm actor should echo on MT runtime");
|
||||
// Poll with retries — MT runtime timing is non-deterministic
|
||||
let mut received = None;
|
||||
for _ in 0..20 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(25));
|
||||
if let Some(msg) = inbox.try_recv() {
|
||||
received = Some(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let received = received.expect("wasm actor should echo on MT runtime");
|
||||
assert_eq!(received.0, payload);
|
||||
|
||||
handle.shutdown();
|
||||
|
|
@ -1841,9 +1850,18 @@ fn wasm_actors_communicate_across_threads() {
|
|||
rt.send_to(addr_a, msg_for_a).unwrap();
|
||||
|
||||
let handle = rt.run().unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
let received = inbox.try_recv().expect("cross-thread relay should deliver");
|
||||
// Poll with retries — MT runtime timing is non-deterministic
|
||||
let mut received = None;
|
||||
for _ in 0..20 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(25));
|
||||
if let Some(msg) = inbox.try_recv() {
|
||||
received = Some(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let received = received.expect("cross-thread relay should deliver");
|
||||
assert_eq!(received.0, final_payload);
|
||||
|
||||
handle.shutdown();
|
||||
|
|
@ -2229,3 +2247,157 @@ proptest! {
|
|||
prop_assert!(result.is_ok(), "actor must survive: alloc={alloc_val} trap={trap_handle} send_before={send_before_trap}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stack overflow: deep recursion in handle ────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_stack_overflow_traps_actor_survives() {
|
||||
// Guest handle calls itself recursively until stack overflow.
|
||||
// Wasmtime should trap with a stack overflow error; actor must survive.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||
(func $recurse (param $ptr i32) (param $len i32)
|
||||
local.get $ptr
|
||||
local.get $len
|
||||
call $recurse
|
||||
)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
local.get $ptr
|
||||
local.get $len
|
||||
call $recurse
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
rt.send_to(addr, ByteMessage(vec![1, 2, 3])).unwrap();
|
||||
rt.tick(); // stack overflow trap
|
||||
|
||||
// Actor survives
|
||||
rt.send_to(addr, ByteMessage(vec![4, 5, 6])).unwrap();
|
||||
rt.tick();
|
||||
}
|
||||
|
||||
// ── Bulk memory: memory.fill and memory.copy ────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn guest_using_bulk_memory_ops_works() {
|
||||
// The engine enables bulk_memory. Guest uses memory.fill to write a
|
||||
// pattern, then sends it. Verifies bulk memory operations work.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 256)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
;; Fill bytes [500..510] with value 42 using memory.fill
|
||||
(memory.fill (i32.const 500) (i32.const 42) (i32.const 10))
|
||||
|
||||
;; Send 10 bytes from [500..510]
|
||||
local.get $ptr
|
||||
i32.const 500
|
||||
i32.const 10
|
||||
call $send
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let inbox = rt.new_inbox::<ByteMessage>().unwrap();
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
rt.send_to(addr, framed_msg(inbox.addr(), b"trigger")).unwrap();
|
||||
rt.tick();
|
||||
|
||||
let received = inbox.try_recv().expect("bulk memory fill should work");
|
||||
assert_eq!(received.0, vec![42u8; 10]);
|
||||
}
|
||||
|
||||
// ── Self-amplification: bounded by message budget ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn self_amplification_bounded_by_budget_no_crash() {
|
||||
// Guest sends 3 copies of the message back to itself. With budget=4
|
||||
// each tick processes at most 4 messages. Run for a few ticks — should
|
||||
// not crash or OOM.
|
||||
let wat = r#"
|
||||
(module
|
||||
(import "swactor" "send" (func $send (param i32 i32 i32)))
|
||||
(memory (export "memory") 1)
|
||||
(func (export "alloc") (param i32) (result i32) i32.const 4096)
|
||||
(func (export "handle") (param $ptr i32) (param $len i32)
|
||||
(local $i i32)
|
||||
(local.set $i (i32.const 0))
|
||||
(block $break
|
||||
(loop $loop
|
||||
(br_if $break (i32.ge_u (local.get $i) (i32.const 3)))
|
||||
local.get $ptr
|
||||
local.get $ptr
|
||||
i32.const 33
|
||||
call $send
|
||||
(local.set $i (i32.add (local.get $i) (i32.const 1)))
|
||||
(br $loop)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, wasm).build().unwrap();
|
||||
|
||||
let config = RuntimeConfig {
|
||||
actor_message_budget: 4,
|
||||
..RuntimeConfig::default()
|
||||
};
|
||||
let rt = Runtime::new(config);
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
// Initial seed: [self_addr | marker]
|
||||
let mut seed = Vec::new();
|
||||
seed.extend_from_slice(&addr.0);
|
||||
seed.push(0xFF);
|
||||
rt.send_to(addr, ByteMessage(seed)).unwrap();
|
||||
|
||||
// Run for 5 ticks — should not crash
|
||||
for _ in 0..5 {
|
||||
rt.tick();
|
||||
}
|
||||
|
||||
// Actor alive
|
||||
rt.send_to(addr, ByteMessage(vec![0])).unwrap();
|
||||
rt.tick();
|
||||
}
|
||||
|
||||
// ── Double stop: stopping an already-stopped actor ──────────────────────────
|
||||
|
||||
#[test]
|
||||
fn double_stop_is_idempotent() {
|
||||
let engine = SharedEngine::new().unwrap();
|
||||
let actor = WasmActorBuilder::new(engine, guest_wasm("silent"))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let rt = Runtime::new(RuntimeConfig::default());
|
||||
let addr = rt.spawn(actor).unwrap();
|
||||
|
||||
rt.stop_actor(addr).unwrap();
|
||||
rt.tick();
|
||||
rt.tick();
|
||||
|
||||
// Second stop should fail gracefully (not panic)
|
||||
let result = rt.stop_actor(addr);
|
||||
assert!(result.is_err(), "stopping already-stopped actor should error");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue