swactor/crates/process/src/actor.rs

284 lines
9.4 KiB
Rust
Raw Normal View History

use std::sync::{Arc, OnceLock};
use swactor::actor::{ActorAddress, ActorInterface, Ctx};
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
use swactor::runtime::ExternalSender;
use crate::lifecycle::PreparedProcessOutput;
use crate::message::{ProcessActorCommand, ProcessCommand, ProcessOutput};
use crate::supervisor::{
ProcessSupervisorThread, ProcessThreadHandle, ThreadEvent, ThreadEventReceiver,
thread_event_channel,
};
use crate::types::{ExitStatus, ProcessSpec};
enum ProcessActorState {
Spawning { stop_requested: bool },
Running,
Stopping,
Done(ProcessDoneState),
}
enum ProcessDoneState {
Exited(ExitStatus),
SpawnFailed(String),
SupervisorFailed(String),
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
impl ProcessDoneState {
fn observe(&self) {
match self {
Self::Exited(status) => {
let _ = *status;
}
Self::SpawnFailed(error) | Self::SupervisorFailed(error) => {
let _ = error.as_str();
}
}
}
}
pub(crate) struct ProcessActor {
spec: Option<ProcessSpec>,
output: PreparedProcessOutput,
sender: ExternalSender,
addr_slot: Arc<OnceLock<ActorAddress>>,
state: ProcessActorState,
pid: Option<u32>,
supervisor: Option<ProcessThreadHandle>,
supervisor_events: Option<ThreadEventReceiver>,
}
impl ProcessActor {
pub(crate) fn new(
spec: ProcessSpec,
output: PreparedProcessOutput,
sender: ExternalSender,
addr_slot: Arc<OnceLock<ActorAddress>>,
) -> Self {
Self {
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
spec: Some(spec),
output,
sender,
addr_slot,
state: ProcessActorState::Spawning {
stop_requested: false,
},
pid: None,
supervisor: None,
supervisor_events: None,
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
fn emit_process_output(&self, ctx: &Ctx, output: ProcessOutput) {
let _ = ctx.send(self.output.upstream, output.clone());
if let Some(mirror) = &self.output.mirror {
mirror.submit(&output);
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
fn apply_stop(&mut self, ctx: &Ctx, kill_after: Option<std::time::Duration>) {
let mut next_state = None;
let mut terminal_error = None;
match &mut self.state {
ProcessActorState::Spawning { stop_requested } => {
if *stop_requested {
return;
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
match self
.supervisor
.as_ref()
.expect("supervisor started before commands")
.stop(kill_after)
{
Ok(()) => *stop_requested = true,
Err(error) => terminal_error = Some(error),
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
}
ProcessActorState::Running => {
match self
.supervisor
.as_ref()
.expect("supervisor started before commands")
.stop(kill_after)
{
Ok(()) => next_state = Some(ProcessActorState::Stopping),
Err(error) => terminal_error = Some(error),
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
}
ProcessActorState::Stopping | ProcessActorState::Done(_) => {}
}
if let Some(state) = next_state {
self.state = state;
}
if let Some(error) = terminal_error {
self.emit_terminal_error(ctx, error);
}
}
fn drain_supervisor_events(&mut self, ctx: &Ctx) {
let events = self
.supervisor_events
.as_ref()
.map(ThreadEventReceiver::drain)
.unwrap_or_default();
for event in events {
self.handle_thread_event(ctx, event);
self.finish_if_safe(ctx);
}
self.finish_if_safe(ctx);
}
fn handle_thread_event(&mut self, ctx: &Ctx, event: ThreadEvent) {
match event {
ThreadEvent::Started { pid } => {
let stop_requested = match &self.state {
ProcessActorState::Spawning { stop_requested } => *stop_requested,
ProcessActorState::Running
| ProcessActorState::Stopping
| ProcessActorState::Done(_) => return,
};
self.pid = Some(pid);
self.emit_process_output(ctx, ProcessOutput::Started { pid });
self.state = if stop_requested {
ProcessActorState::Stopping
} else {
ProcessActorState::Running
};
}
ThreadEvent::SpawnFailed { error } => {
if !matches!(self.state, ProcessActorState::Done(_)) {
self.emit_process_output(
ctx,
ProcessOutput::SpawnFailed {
error: error.clone(),
},
);
self.state = ProcessActorState::Done(ProcessDoneState::SpawnFailed(error));
}
}
ThreadEvent::Exited { status } => {
if !matches!(self.state, ProcessActorState::Done(_)) {
self.emit_process_output(ctx, ProcessOutput::Exited { status });
self.state = ProcessActorState::Done(ProcessDoneState::Exited(status));
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
}
ThreadEvent::Error { error } => {
if !matches!(self.state, ProcessActorState::Done(_)) {
self.emit_process_output(
ctx,
ProcessOutput::Error {
error: error.clone(),
},
);
self.state = ProcessActorState::Done(ProcessDoneState::SupervisorFailed(error));
}
}
ThreadEvent::ThreadFinished => {
if let Some(supervisor) = self.supervisor.as_mut() {
match supervisor.join_if_finished() {
Ok(_) => {}
Err(_) => {
if !matches!(self.state, ProcessActorState::Done(_)) {
let error = "process supervisor thread failed".to_owned();
self.emit_process_output(
ctx,
ProcessOutput::Error {
error: error.clone(),
},
);
self.state = ProcessActorState::Done(
ProcessDoneState::SupervisorFailed(error),
);
}
}
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
self.supervisor = None;
self.supervisor_events = None;
}
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
fn finish_if_safe(&mut self, ctx: &Ctx) {
if let ProcessActorState::Done(done) = &self.state {
done.observe();
let _ = self.pid;
if self.supervisor.is_none() {
ctx.stop_self();
}
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
fn emit_terminal_error(&mut self, ctx: &Ctx, error: String) {
if matches!(self.state, ProcessActorState::Done(_)) {
return;
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
self.emit_process_output(
ctx,
ProcessOutput::Error {
error: error.clone(),
},
);
self.state = ProcessActorState::Done(ProcessDoneState::SupervisorFailed(error));
ctx.stop_self();
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
impl ActorInterface for ProcessActor {
type Incoming = ProcessActorCommand;
type Response = ();
fn on_start(&mut self, ctx: &Ctx) {
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
let sender = self.sender.clone();
let addr_slot = self.addr_slot.clone();
let (event_sink, event_receiver) = thread_event_channel(move || {
if let Some(addr) = addr_slot.get() {
let _ = sender.send_to(*addr, ProcessActorCommand::SupervisorWake);
}
});
let spec = self.spec.take().expect("process spec already taken");
match ProcessSupervisorThread::start(spec, event_sink) {
Ok(supervisor) => {
self.supervisor = Some(supervisor);
self.supervisor_events = Some(event_receiver);
self.drain_supervisor_events(ctx);
}
Err(err) => {
let error = format!("process supervisor thread failed: {err}");
self.emit_process_output(
ctx,
ProcessOutput::Error {
error: error.clone(),
},
);
self.state = ProcessActorState::Done(ProcessDoneState::SupervisorFailed(error));
ctx.stop_self();
}
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
fn handle(&mut self, ctx: &Ctx, msg: ProcessActorCommand) {
match msg {
ProcessActorCommand::SupervisorWake => self.drain_supervisor_events(ctx),
ProcessActorCommand::Command(ProcessCommand::Stop { kill_after }) => {
self.drain_supervisor_events(ctx);
self.apply_stop(ctx, kill_after);
self.drain_supervisor_events(ctx);
}
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
}
refactor(process): process-manager cleanup Replace the driver/session/action/event abstraction with a single OS-process supervisor thread and a minimal lifecycle-only public API. - supervisor: add a dedicated swactor-process-supervisor thread that owns the child, runs it with null stdio, wakes via an eventfd plus poll(2), reaps with waitpid(WNOHANG), and escalates SIGTERM to SIGKILL after a deadline, reporting only lifecycle ThreadEvents over a SegQueue plus wake channel - actor: collapse ProcessActor<D> into a non-generic state machine (Spawning/Running/Stopping/Done) that owns the supervisor handle, drains events on SupervisorWake, forwards lifecycle as ProcessOutput, and triggers shutdown_now in on_stop - lifecycle: add ProcessOutputConfig (Disabled/DatastreamMirror) with a JSON proc.<label>.lifecycle mirror (schema swactor_process.lifecycle.v1), command-basename label derivation/sanitization, and an RAII reservation registry preventing duplicate channels - message/types/spawn/lib: trim the API — ProcessCommand is now only Stop { kill_after }, ProcessOutput covers Started/SpawnFailed/Exited/Error, ProcessSpec keeps command/args/env/working_dir/label; re-export spawn_local_process/send_process_command and drop the custom-driver spawn_process - removed: delete the action/event/local/mock/session modules and the ProcessDriver/ProcessWaker/EventQueue/PtySize/ProcessMode types plus the old test suite (actor_scenarios, e2e_process, local_driver, proptest_session, session_scenarios); add public_api_stage1/2 tests and the SWACTOR_MANAGED_PROCESS_SPEC.md - swactor core: demote ProcessOutputObserver to a legacy/custom adapter (no longer auto-attached), remove Runtime::set_process_output_observer and Ctx::process_output_observer, and add the datastream dependency to the process crate for the mirror Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-07-18 11:21:34 +00:00
fn on_stop(&mut self, _ctx: &Ctx) {
if let Some(supervisor) = &self.supervisor {
let _ = supervisor.shutdown_now();
}
}
}