--- a/init/src/scheduler.rs 2026-05-04 23:20:24.735668104 +0100 +++ b/init/src/scheduler.rs 2026-05-05 09:36:09.955622123 +0100 @@ -1,11 +1,14 @@ -use std::collections::VecDeque; +use std::collections::{BTreeSet, VecDeque}; use crate::InitConfig; -use crate::color::{init_debug, init_error, init_info, status_ok, status_skip}; +use crate::color::{init_error, status_ok, status_skip}; use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; +const SPAWN_BATCH_SIZE: usize = 50; + pub struct Scheduler { pending: VecDeque, + completed: BTreeSet, } struct Job { @@ -21,6 +24,7 @@ pub fn new() -> Scheduler { Scheduler { pending: VecDeque::new(), + completed: BTreeSet::new(), } } @@ -29,10 +33,11 @@ unit_store: &mut UnitStore, unit_id: UnitId, ) { + let id_str = unit_id.0.clone(); let mut errors = vec![]; self.schedule_start(unit_store, unit_id, &mut errors); - for error in errors { - init_error(&format!("{}", error)); + for error in &errors { + init_error(&format!("{}: {}", id_str, error)); } } @@ -55,7 +60,14 @@ } } + pub fn has_pending(&self) -> bool { + !self.pending.is_empty() + } + pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) { + let mut defer_count: usize = 0; + let mut spawned_this_step: usize = 0; + 'a: loop { let Some(job) = self.pending.pop_front() else { return; @@ -63,18 +75,44 @@ match job.kind { JobKind::Start => { - let unit = unit_store.unit_mut(&job.unit); - - for dep in &unit.info.requires_weak { - for pending_job in &self.pending { - if &pending_job.unit == dep { - self.pending.push_back(job); - continue 'a; + let deps_ok = { + let unit = unit_store.unit(&job.unit); + let mut ok = true; + for dep in &unit.info.requires_weak { + if self.completed.contains(dep) { + continue; + } + if !unit_store.has_unit(dep) { + continue; } + let in_pending = self.pending.iter().any(|pj| &pj.unit == dep); + if in_pending { + ok = false; + break; + } + } + ok + }; + + if !deps_ok { + defer_count += 1; + self.pending.push_back(job); + if defer_count > self.pending.len() + self.completed.len() { + return; } + continue 'a; } + defer_count = 0; + + let unit = unit_store.unit_mut(&job.unit); run(unit, init_config); + self.completed.insert(job.unit); + spawned_this_step += 1; + + if spawned_this_step >= SPAWN_BATCH_SIZE { + return; + } } } } @@ -85,9 +123,6 @@ match &unit.kind { UnitKind::LegacyScript { script } => { for cmd in script.clone() { - if config.log_debug { - init_debug(&format!("running: {cmd:?}")); - } cmd.run(config); } } @@ -97,18 +132,9 @@ status_skip(&format!("Skipping {} ({})", desc, service.cmd)); return; } - if config.log_debug { - init_info(&format!("Starting {} ({})", desc, service.cmd)); - } else { - status_ok(&format!("Started {}", desc)); - } + status_ok(&format!("Started {}", desc)); service.spawn(&config.envs); } - UnitKind::Target {} => { - if config.log_debug { - init_info(&format!("Reached target {}", - unit.info.description.as_ref().unwrap_or(&unit.id.0))); - } - } + UnitKind::Target {} => {} } } --- a/init/src/service.rs 2026-05-04 23:20:24.736497941 +0100 +++ b/init/src/service.rs 2026-05-05 08:47:30.832599161 +0100 @@ -47,73 +47,91 @@ } command.envs(base_envs).envs(&self.envs); - let (mut read_pipe, write_pipe) = io::pipe().unwrap(); - unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) }; - - let mut child = match command.spawn() { - Ok(child) => child, - Err(err) => { - status_fail(&format!("failed to execute {:?}: {}", command, err)); - return; - } - }; - match &self.type_ { - ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { - Ok(()) => {} - Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { - init_warn(&format!("{:?} exited without notifying readiness", command)); - } - Err(err) => { - init_error(&format!("failed to wait for {:?}: {}", command, err)); + ServiceType::OneshotAsync => { + match command.spawn() { + Ok(_child) => {} + Err(err) => { + status_fail(&format!("failed to execute {:?}: {}", command, err)); + } } - }, - ServiceType::Scheme(scheme) => { - let mut new_fd = usize::MAX; - loop { - match syscall::call_ro( - read_pipe.as_raw_fd() as usize, - unsafe { plain::as_mut_bytes(&mut new_fd) }, - syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER, - &[], - ) { - Err(syscall::Error { - errno: syscall::EINTR, - }) => continue, - Ok(0) => { + } + _ => { + let (mut read_pipe, write_pipe) = io::pipe().unwrap(); + let write_fd: OwnedFd = write_pipe.into(); + let write_raw = write_fd.as_raw_fd(); + unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_fd) }; + + let mut child = match command.spawn() { + Ok(child) => child, + Err(err) => { + let _ = unsafe { libc::close(write_raw) }; + drop(read_pipe); + status_fail(&format!("failed to execute {:?}: {}", command, err)); + return; + } + }; + + let _ = unsafe { libc::close(write_raw) }; + + match &self.type_ { + ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { + Ok(()) => {} + Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { init_warn(&format!("{:?} exited without notifying readiness", command)); - return; - } - Ok(1) => break, - Ok(n) => { - init_error(&format!("incorrect amount of fds {} returned", n)); - return; } Err(err) => { init_error(&format!("failed to wait for {:?}: {}", command, err)); - return; } - } - } - - let current_namespace_fd = libredox::call::getns().expect("TODO"); - libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) - .expect("TODO"); - } - ServiceType::Oneshot => { - drop(read_pipe); - match child.wait() { - Ok(exit_status) => { - if !exit_status.success() { - status_fail(&format!("{:?} failed with {}", command, exit_status)); + }, + ServiceType::Scheme(scheme) => { + let mut new_fd = usize::MAX; + loop { + match syscall::call_ro( + read_pipe.as_raw_fd() as usize, + unsafe { plain::as_mut_bytes(&mut new_fd) }, + syscall::CallFlags::FD | syscall::CallFlags::FD_UPPER, + &[], + ) { + Err(syscall::Error { + errno: syscall::EINTR, + }) => continue, + Ok(0) => { + init_warn(&format!("{:?} exited without notifying readiness", command)); + return; + } + Ok(1) => break, + Ok(n) => { + init_error(&format!("incorrect amount of fds {} returned", n)); + return; + } + Err(err) => { + init_error(&format!("failed to wait for {:?}: {}", command, err)); + return; + } + } } + + let current_namespace_fd = libredox::call::getns().expect("TODO"); + libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) + .expect("TODO"); } - Err(err) => { - init_error(&format!("failed to wait for {:?}: {}", command, err)) + ServiceType::Oneshot => { + drop(read_pipe); + match child.wait() { + Ok(exit_status) => { + if !exit_status.success() { + status_fail(&format!("{:?} failed with {}", command, exit_status)); + } + } + Err(err) => { + init_error(&format!("failed to wait for {:?}: {}", command, err)) + } + } } + ServiceType::OneshotAsync => unreachable!(), } } - ServiceType::OneshotAsync => {} } } } @@ -122,7 +140,6 @@ cmd.env(env, format!("{}", fd.as_raw_fd())); unsafe { cmd.pre_exec(move || { - // Pass notify pipe to child if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 { Err(io::Error::last_os_error()) } else { --- a/init/src/unit.rs 2026-05-04 23:40:38.259604979 +0100 +++ b/init/src/unit.rs 2026-05-05 08:47:30.832676105 +0100 @@ -101,6 +101,10 @@ pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit { self.units.get_mut(unit).unwrap() } + + pub fn has_unit(&self, unit: &UnitId) -> bool { + self.units.contains_key(unit) + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)] --- a/init/src/main.rs 2026-05-04 23:20:24.737380326 +0100 +++ b/init/src/main.rs 2026-05-05 09:09:22.005434517 +0100 @@ -137,10 +137,16 @@ Path::new("/usr"), Path::new("/etc"), ); - { - // FIXME introduce multi-user.target unit and replace the config dir iteration - // scheduler.schedule_start_and_report_errors(&mut unit_store, UnitId("multi-user.target".to_owned())); + if let Some(skip_val) = init_config.envs.get("INIT_SKIP") { + if let Some(skip_str) = skip_val.to_str() { + if !skip_str.is_empty() { + init_config.skip_cmd = skip_str.split(',').map(|s| s.to_string()).collect(); + } + } + } + + { let entries = match config::config_for_dirs(&unit_store.config_dirs) { Ok(entries) => entries, Err(err) => { @@ -167,11 +173,14 @@ libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); loop { - // Process any pending jobs whose dependencies may now be satisfied. - scheduler.step(&mut unit_store, &mut init_config); + if scheduler.has_pending() { + // Reap exited children before processing more services. + let mut status = 0; + while libredox::call::waitpid(0, &mut status, 1).is_ok() {} + + scheduler.step(&mut unit_store, &mut init_config); + } - // Non-blocking wait: WNOHANG = 1. Reap any exited children without - // blocking, then loop back to process any newly-unblocked pending jobs. let mut status = 0; match libredox::call::waitpid(0, &mut status, 1) { Ok(_pid) => {}