diff --git a/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md b/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md index 751f3ccb5de..a417779fcb7 100644 --- a/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md +++ b/issues/a-childs-end-is-an-event-and-a-parent-takes-its-children-down.md @@ -168,7 +168,8 @@ an explicit force issues it, and never a timer's. `libc_children`'s QEMU x86-64 boot by `signal_sigchld_ignored`, `signal_sigchld_ppoll` and `libc_read_restarts`, each red under its mutation and the stage reverted whole, beside `signal_alarm`; the routing laws are - host tests (`signal_dispositions.rs`). libc's own thread also watches its + host tests (`signal_dispositions.rs`), and so is a wait deciding from one + read of each child's end (`spawn_requests.rs`). libc's own thread also watches its control pipe, so it watches at most 254 children: the 255th is refused `EAGAIN`, a `poll` takes 255 descriptors beside its wake pipe, and `SIG_IGN` is shown over 255 children. diff --git a/sdk/libc/arch/src/spawn_requests.rs b/sdk/libc/arch/src/spawn_requests.rs index bb5d37decf2..fda8fd54f5f 100644 --- a/sdk/libc/arch/src/spawn_requests.rs +++ b/sdk/libc/arch/src/spawn_requests.rs @@ -7,7 +7,7 @@ use toyos_abi::end::{End, Fault}; use crate::errcode::{EBADF, EINVAL}; use crate::header::{self, FCNTL_H, SIGNAL_H}; -use crate::spawnreq::{any_fd, child_fd, plan, wait_status, Action, Plan}; +use crate::spawnreq::{any_fd, child_fd, plan, turn, wait_status, Action, Plan, Turn}; const STDIN: u32 = 0x1000; const STDOUT: u32 = 0x1001; @@ -183,3 +183,22 @@ fn each_end_reads_as_its_wait_status() { assert!(!wifexited(s) && wtermsig(s) == signal(name), "{end:?}: {s:#x}"); } } + +/// **With `SIGCHLD` ignored a wait answers no child**: a child that ended is +/// forgotten, and a wait whose every child ended answers `ECHILD`. +#[test] +fn an_ignored_sigchld_answers_no_ended_child() { + assert_eq!(turn(&[true], true), Turn::Gone); + assert_eq!(turn(&[true, true], true), Turn::Gone); + assert_eq!(turn(&[true, false], true), Turn::Watch); + assert_eq!(turn(&[false], true), Turn::Watch); + assert_eq!(turn(&[], true), Turn::Gone); +} + +/// A wait otherwise answers the first named child that ended. +#[test] +fn a_wait_takes_the_first_ended_child() { + assert_eq!(turn(&[false, true, true], false), Turn::Take(1)); + assert_eq!(turn(&[false, false], false), Turn::Watch); + assert_eq!(turn(&[], false), Turn::Gone); +} diff --git a/sdk/libc/src/spawn.rs b/sdk/libc/src/spawn.rs index 3e96f10e695..54e28638c75 100644 --- a/sdk/libc/src/spawn.rs +++ b/sdk/libc/src/spawn.rs @@ -30,7 +30,7 @@ use toyos_abi::RawHandle; use crate::errcode::{E2BIG, EACCES, EAGAIN, EBADF, ECHILD, EINTR, EINVAL, ENOENT, EPERM, ESRCH}; use crate::pthread::Lock; -use crate::spawnreq::{self, Action}; +use crate::spawnreq::{self, Action, Turn}; const SIGKILL: i32 = 9; const WNOHANG: i32 = 1; @@ -404,13 +404,20 @@ fn reap(pid: i32) -> Option<(i32, End, toyos_abi::syscall::ProcessStats)> { /// Every child that has ended, out of the table and kept for no wait, as /// `SIGCHLD` ignored has it. pub(crate) fn forget_ended() { - let ended: Vec = { - let mut children = CHILDREN.lock(); - let (ended, live) = core::mem::take(&mut *children).into_iter().partition(|c| c.process.try_wait().is_ok()); - *children = live; - ended - }; - UNWAITED.fetch_sub(ended.len(), Ordering::Relaxed); + let mut children = CHILDREN.lock(); + let ended: Vec = children.iter().map(|c| c.process.try_wait().is_ok()).collect(); + forget(&mut children, &ended); +} + +/// Take the children `ended` read as ended out of `children`, kept for no +/// wait. +fn forget(children: &mut Vec, ended: &[bool]) { + let mut at = 0; + children.retain(|_| { + at += 1; + !ended[at - 1] + }); + UNWAITED.fetch_sub(ended.iter().filter(|&&e| e).count(), Ordering::Relaxed); } /// Each child whose end has not raised `SIGCHLD`, by pid. @@ -461,30 +468,35 @@ fn wait_child(pid: i32, block: bool) -> Result = { - let children = CHILDREN.lock(); - let named: Vec<&Child> = children.iter().filter(|c| names(pid, c)).collect(); - if named.is_empty() { - return Err(ECHILD); + let mut children = CHILDREN.lock(); + // Each child's end is read once, and that read alone decides: a + // child that ends after it is the next turn's. + let ended: Vec = children.iter().map(|c| c.process.try_wait().is_ok()).collect(); + let named: Vec = (0..children.len()).filter(|&i| names(pid, &children[i])).collect(); + let named_ended: Vec = named.iter().map(|&i| ended[i]).collect(); + let turn = spawnreq::turn(&named_ended, ignored); + if ignored { + forget(&mut children, &ended); } - if let Some(ended) = named.iter().find(|c| c.process.try_wait().is_ok()) { - let pid = ended.pid; - drop(children); - if let Some(reaped) = reap(pid) { - return Ok(Some(reaped)); + let running: Vec<&Child> = match turn { + Turn::Gone => return Err(ECHILD), + Turn::Take(at) => { + let pid = children[named[at]].pid; + drop(children); + if let Some(reaped) = reap(pid) { + return Ok(Some(reaped)); + } + continue; } - continue; - } - if !block { - return Ok(None); - } - let mut copies = Vec::with_capacity(named.len()); - for child in named { + Turn::Watch if !block => return Ok(None), + Turn::Watch => children.iter().filter(|c| names(pid, c)).collect(), + }; + let mut copies = Vec::with_capacity(running.len()); + for child in running { match syscall::dup(toyos::AsHandle::as_handle(&child.process)) { Ok(copy) => copies.push((child.pid, copy)), Err(e) => { diff --git a/sdk/libc/src/spawnreq.rs b/sdk/libc/src/spawnreq.rs index d6c83c3f7d7..d94c1b35ae8 100644 --- a/sdk/libc/src/spawnreq.rs +++ b/sdk/libc/src/spawnreq.rs @@ -116,6 +116,32 @@ pub(crate) fn plan

( Ok(Plan { fds, opened }) } +/// What one turn of a wait does with the children it names. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Turn { + /// None is left to wait for: `ECHILD`. + Gone, + /// The named child at this index has ended and is answered. + Take(usize), + /// Every named child left is running. + Watch, +} + +/// The turn for the named children whose ends `ended` holds, each read once. +/// **With `SIGCHLD` ignored an ended child is forgotten, never answered**: the +/// caller drops every child this read saw ended, and a child that ends after +/// the read is the next turn's. +pub(crate) fn turn(ended: &[bool], ignored: bool) -> Turn { + if ignored { + return if ended.contains(&false) { Turn::Watch } else { Turn::Gone }; + } + match ended.iter().position(|&e| e) { + Some(at) => Turn::Take(at), + None if ended.is_empty() => Turn::Gone, + None => Turn::Watch, + } +} + /// The status `waitpid` writes for an end, as ``'s macros read /// it: an exit's low byte above the low eight bits, or the signal /// `toyos::process::signal` names the end by in the low seven.