From 333a4de037ed7b28ef9fa9c2ce8c58f90ab49640 Mon Sep 17 00:00:00 2001 From: Trevor Cappallo Date: Tue, 11 Nov 2025 12:50:45 -0500 Subject: [PATCH] test: cover multi-level actor supervision propagation --- STATUS.md | 10 +++- examples/actor_supervision.lx | 91 +++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/STATUS.md b/STATUS.md index 2274655..b09b1c2 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,6 +1,6 @@ # Lx Implementation Status Report -**Last Updated:** November 11, 2025 +**Last Updated:** November 12, 2025 **Overall Progress:** ~84% (Core language ~86% complete, LLM-first tooling ~86% complete, Concurrency ~80% complete) The Lx project has a working **minimal interpreter** covering the foundational subset described in the ROADMAP. Here's the breakdown: @@ -152,12 +152,13 @@ The Lx project has a working **minimal interpreter** covering the foundational s ## 🎯 Working Examples -The implementation successfully runs 36 example files (27 runnable + 9 error test cases) including: +The implementation successfully runs 37 example files (28 runnable + 9 error test cases) including: - ✅ `option.lx` - Sum types, pattern matching - ✅ `contracts.lx` - Contract enforcement - ✅ `logging.lx` - Effect tracking - ✅ `median.lx` - Pure functions with tests - ✅ `result.lx` - Error handling patterns +- ✅ `actor_supervision.lx` - Supervision restarts and multi-level failure propagation - ✅ `property_basics.lx` - Property-based testing with predicates and assertions - ✅ `property_shrinking.lx` - Counterexample shrinking for property tests - ✅ `property_deterministic.lx` - Deterministic property testing with --seed flag @@ -376,6 +377,11 @@ Phase 5 (Long-term): Evolution - Created `examples/property_deterministic.lx` demonstrating deterministic property tests - Same seed produces reproducible test results for debugging and replay +**Recent Work (November 12, 2025):** +- ✅ Hardened actor supervision runtime with multi-level failure coverage + - Added `RootSupervisor` / `SupervisorNoHandler` actors in `examples/actor_supervision.lx` + - New regression test `failure_propagates_without_intermediate_handler` proves `ChildFailed` notifications bubble past supervisors that lack handlers + With the core language, schemas, LLM tooling (including deterministic execution), and actor runtime (including async_group) mostly complete, the next priorities are: 1. **Actor Runtime Enhancements** (Priority 8, continuing): diff --git a/examples/actor_supervision.lx b/examples/actor_supervision.lx index 74748c2..7399fc2 100644 --- a/examples/actor_supervision.lx +++ b/examples/actor_supervision.lx @@ -77,6 +77,74 @@ actor Supervisor() { } } +actor SupervisorNoHandler() { + state { + worker: ActorRef + } + + on Start() -> [Concurrent] ActorRef { + let spawned = Worker.spawn() + let worker = spawned + return spawned + } + + on Send(value: Int) -> [Concurrent] Unit { + worker.send(DoWork { value: value }) + } + + on Crash() -> [Concurrent] Unit { + worker.send(DoWork { value: -1 }) + } + + on CurrentWorker() -> [Concurrent] ActorRef { + return worker + } +} + +actor RootSupervisor() { + state { + supervisor: ActorRef + last_failure: String + last_message: String + last_actor: String + last_child: ActorRef + } + + on Start() -> [Concurrent] ActorRef { + let supervisor = SupervisorNoHandler.spawn() + let initial_worker = SupervisorNoHandler.Start(supervisor) + let last_child = initial_worker + let last_failure = "" + let last_message = "" + let last_actor = "" + return supervisor + } + + on Send(value: Int) -> [Concurrent] Unit { + supervisor.send(Send { value: value }) + } + + on Crash() -> [Concurrent] Unit { + supervisor.send(Crash { }) + } + + on ChildFailed(child: ActorRef, reason: String, message: String, actor: String) -> [Concurrent] Unit { + let last_child = child + let last_failure = reason + let last_message = message + let last_actor = actor + } + + on FailureSummary() -> [Concurrent] SupervisorMsg { + return ChildFailed { + child: last_child + reason: last_failure + message: last_message + actor: last_actor + } + } +} + test supervisor_restarts_child { let supervisor = Supervisor.spawn() Supervisor.Start(supervisor) @@ -111,3 +179,26 @@ test supervisor_restarts_child { } } } + +test failure_propagates_without_intermediate_handler { + let root = RootSupervisor.spawn() + let supervisor = RootSupervisor.Start(root) + let worker = SupervisorNoHandler.CurrentWorker(supervisor) + + RootSupervisor.Crash(root) + Concurrent.flush() + Concurrent.flush() + + let summary = RootSupervisor.FailureSummary(root) + match summary { + case ChildFailed { child: failed_child , reason: failure_reason , message: failure_message , actor: failure_actor } => { + test.assert_equal(worker, failed_child) + test.assert_equal("assertion failed", failure_reason) + test.assert_equal("DoWork", failure_message) + test.assert_equal("Worker", failure_actor) + } + case _ => { + assert(false) + } + } +}