init: add serial debug output for scheduler tracing

Add dbg_init/dbgprintln macros that write directly to /scheme/debug/no-preserve,
bypassing logd output redirection after switch_stdio. This enables scheduler
tracing (INIT_RUN, INIT_DEFER, INIT_BLOCK, INIT_DONE, INIT_SCHEME) to remain
visible on the serial console throughout all boot phases.

Also add INIT_SPIN counter to detect infinite polling loops in step().
This commit is contained in:
Red Bear OS
2026-07-03 08:53:20 +03:00
parent a0b05b1fc0
commit b1a6bd871f
4 changed files with 127 additions and 21 deletions
+45 -1
View File
@@ -1,6 +1,8 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::ffi::OsString; use std::ffi::OsString;
use std::io::Write;
use std::path::Path; use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{env, fs, io}; use std::{env, fs, io};
use libredox::flag::{O_RDONLY, O_WRONLY}; use libredox::flag::{O_RDONLY, O_WRONLY};
@@ -13,6 +15,43 @@ mod script;
mod service; mod service;
mod unit; mod unit;
static SERIAL_DEBUG_FD: std::sync::OnceLock<fs::File> = std::sync::OnceLock::new();
static SERIAL_DEBUG_INIT: AtomicBool = AtomicBool::new(false);
pub fn dbg_init() {
SERIAL_DEBUG_INIT.store(true, Ordering::SeqCst);
let _ = SERIAL_DEBUG_FD.get_or_init(|| {
fs::OpenOptions::new()
.write(true)
.open("/scheme/debug/no-preserve")
.unwrap_or_else(|_| {
fs::OpenOptions::new()
.write(true)
.open("/scheme/debug")
.unwrap_or_else(|_| fs::OpenOptions::new().write(true).open("/dev/null").unwrap())
})
});
}
pub fn dbg(msg: &str) {
if !SERIAL_DEBUG_INIT.load(Ordering::SeqCst) {
eprintln!("{}", msg);
return;
}
if let Some(f) = SERIAL_DEBUG_FD.get() {
let mut f = f;
let _ = writeln!(f, "{}", msg);
let _ = f.flush();
}
}
#[macro_export]
macro_rules! dbgprintln {
($($arg:tt)*) => {
$crate::dbg(&format!($($arg)*))
};
}
fn switch_stdio(stdio: &str) -> io::Result<()> { fn switch_stdio(stdio: &str) -> io::Result<()> {
let stdin = libredox::Fd::open(stdio, O_RDONLY, 0)?; let stdin = libredox::Fd::open(stdio, O_RDONLY, 0)?;
let stdout = libredox::Fd::open(stdio, O_WRONLY, 0)?; let stdout = libredox::Fd::open(stdio, O_WRONLY, 0)?;
@@ -54,6 +93,7 @@ fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Pat
prefix.display(), prefix.display(),
etcdir.display() etcdir.display()
); );
crate::dbgprintln!("init: switchroot to {} {}", prefix.display(), etcdir.display());
config config
.envs .envs
@@ -113,6 +153,7 @@ fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Pat
} }
fn main() { fn main() {
dbg_init();
let mut init_config = InitConfig::new(); let mut init_config = InitConfig::new();
let mut unit_store = UnitStore::new(); let mut unit_store = UnitStore::new();
let mut scheduler = Scheduler::new(); let mut scheduler = Scheduler::new();
@@ -173,8 +214,11 @@ fn main() {
} }
}; };
eprintln!("INIT: starting /usr scheduler step");
crate::dbgprintln!("INIT: starting /usr scheduler step");
scheduler.step(&mut unit_store, &mut init_config); scheduler.step(&mut unit_store, &mut init_config);
eprintln!("INIT: scheduler step complete, entering waitpid loop");
crate::dbgprintln!("INIT: scheduler step complete, entering waitpid loop");
libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
loop { loop {
+59 -10
View File
@@ -55,7 +55,21 @@ impl Scheduler {
} }
pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) { pub fn step(&mut self, unit_store: &mut UnitStore, init_config: &mut InitConfig) {
let mut pushback_count: std::collections::BTreeMap<String, usize> = std::collections::BTreeMap::new();
let mut total_iters: usize = 0;
let mut last_logged_spin: usize = 0;
'a: loop { 'a: loop {
total_iters += 1;
if total_iters - last_logged_spin >= 50 {
last_logged_spin = total_iters;
let pending_names: Vec<&String> = self.pending.iter().map(|j| &j.unit.0).collect();
crate::dbgprintln!(
"INIT_SPIN: {} iterations, {} pending: {:?}",
total_iters,
self.pending.len(),
pending_names
);
}
let Some(job) = self.pending.pop_front() else { let Some(job) = self.pending.pop_front() else {
return; return;
}; };
@@ -64,14 +78,33 @@ impl Scheduler {
JobKind::Start => { JobKind::Start => {
let unit = unit_store.unit_mut(&job.unit); let unit = unit_store.unit_mut(&job.unit);
let mut blocked_by = vec![];
for dep in &unit.info.requires_weak { for dep in &unit.info.requires_weak {
for pending_job in &self.pending { for pending_job in &self.pending {
if &pending_job.unit == dep { if &pending_job.unit == dep {
blocked_by.push(dep.0.clone());
break;
}
}
}
if !blocked_by.is_empty() {
*pushback_count.entry(job.unit.0.clone()).or_insert(0) += 1;
let count = pushback_count[&job.unit.0];
if count <= 3 {
crate::dbgprintln!(
"INIT_DEFER: {} waiting on {:?} (attempt {})",
job.unit.0, blocked_by, count
);
} else if count == 10 {
crate::dbgprintln!(
"INIT_DEFER: {} still waiting on {:?} after 10 attempts - POSSIBLE DEADLOCK",
job.unit.0, blocked_by
);
}
self.pending.push_back(job); self.pending.push_back(job);
continue 'a; continue 'a;
} }
}
}
run(unit, init_config); run(unit, init_config);
} }
@@ -81,32 +114,48 @@ impl Scheduler {
} }
fn run(unit: &mut Unit, config: &mut InitConfig) { fn run(unit: &mut Unit, config: &mut InitConfig) {
crate::dbgprintln!("INIT_RUN: {} ({:?})", unit.id.0, unit.kind);
match &unit.kind { match &unit.kind {
UnitKind::LegacyScript { script } => { UnitKind::LegacyScript { script } => {
for cmd in script.clone() { for cmd in script.clone() {
if config.log_debug { if config.log_debug {
eprintln!("init: running: {cmd:?}"); crate::dbgprintln!("init: running: {cmd:?}");
} }
cmd.run(config); cmd.run(config);
} }
} }
UnitKind::Service { service } => { UnitKind::Service { service } => {
if config.skip_cmd.contains(&service.cmd) { if config.skip_cmd.contains(&service.cmd) {
eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); crate::dbgprintln!("Skipping '{} {}'", service.cmd, service.args.join(" "));
return; return;
} }
if config.log_debug { match &service.type_ {
eprintln!( crate::service::ServiceType::Oneshot
"Starting {} ({})", | crate::service::ServiceType::Notify
unit.info.description.as_ref().unwrap_or(&unit.id.0), | crate::service::ServiceType::Scheme(_) => {
service.cmd, crate::dbgprintln!(
"INIT_BLOCK: {} ({:?} {})",
unit.id.0, service.type_, service.cmd
); );
} }
crate::service::ServiceType::OneshotAsync => {}
}
service.spawn(&config.envs); service.spawn(&config.envs);
match &service.type_ {
crate::service::ServiceType::Oneshot
| crate::service::ServiceType::Notify
| crate::service::ServiceType::Scheme(_) => {
crate::dbgprintln!(
"INIT_DONE: {} ({:?})",
unit.id.0, service.type_
);
}
crate::service::ServiceType::OneshotAsync => {}
}
} }
UnitKind::Target {} => { UnitKind::Target {} => {
if config.log_debug { if config.log_debug {
eprintln!( crate::dbgprintln!(
"Reached target {}", "Reached target {}",
unit.info.description.as_ref().unwrap_or(&unit.id.0), unit.info.description.as_ref().unwrap_or(&unit.id.0),
); );
+21 -9
View File
@@ -52,7 +52,7 @@ impl Service {
let mut child = match command.spawn() { let mut child = match command.spawn() {
Ok(child) => child, Ok(child) => child,
Err(err) => { Err(err) => {
eprintln!("init: failed to execute {:?}: {}", command, err); crate::dbgprintln!("init: failed to execute {:?}: {}", command, err);
return; return;
} }
}; };
@@ -61,13 +61,17 @@ impl Service {
ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
Ok(()) => {} Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
eprintln!("init: {command:?} exited without notifying readiness"); crate::dbgprintln!("init: {command:?} exited without notifying readiness");
} }
Err(err) => { Err(err) => {
eprintln!("init: failed to wait for {command:?}: {err}"); crate::dbgprintln!("init: failed to wait for {command:?}: {err}");
} }
}, },
ServiceType::Scheme(scheme) => { ServiceType::Scheme(scheme) => {
crate::dbgprintln!(
"INIT_SCHEME: entering call_ro loop for {:?} (scheme={}, pipe_fd={})",
command, scheme, read_pipe.as_raw_fd()
);
let mut new_fd = usize::MAX; let mut new_fd = usize::MAX;
loop { loop {
match syscall::call_ro( match syscall::call_ro(
@@ -80,35 +84,43 @@ impl Service {
errno: syscall::EINTR, errno: syscall::EINTR,
}) => continue, }) => continue,
Ok(0) => { Ok(0) => {
eprintln!("init: {command:?} exited without notifying readiness"); crate::dbgprintln!("INIT_SCHEME: call_ro returned Ok(0) for {:?} - writer closed", command);
crate::dbgprintln!("init: {command:?} exited without notifying readiness");
return; return;
} }
Ok(1) => break, Ok(1) => {
crate::dbgprintln!("INIT_SCHEME: call_ro returned Ok(1) for {:?} - got fd={}", command, new_fd);
break;
}
Ok(n) => { Ok(n) => {
eprintln!("init: incorrect amount of fds {n} returned"); crate::dbgprintln!("INIT_SCHEME: call_ro returned Ok({}) for {:?}", n, command);
crate::dbgprintln!("init: incorrect amount of fds {n} returned");
return; return;
} }
Err(err) => { Err(err) => {
eprintln!("init: failed to wait for {command:?}: {err}"); crate::dbgprintln!("INIT_SCHEME: call_ro returned Err({}) for {:?}", err, command);
crate::dbgprintln!("init: failed to wait for {command:?}: {err}");
return; return;
} }
} }
} }
crate::dbgprintln!("INIT_SCHEME: registering scheme {} with fd {} in namespace", scheme, new_fd);
let current_namespace_fd = libredox::call::getns().expect("TODO"); let current_namespace_fd = libredox::call::getns().expect("TODO");
libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
.expect("TODO"); .expect("TODO");
crate::dbgprintln!("INIT_SCHEME: scheme {} registered successfully", scheme);
} }
ServiceType::Oneshot => { ServiceType::Oneshot => {
drop(read_pipe); drop(read_pipe);
match child.wait() { match child.wait() {
Ok(exit_status) => { Ok(exit_status) => {
if !exit_status.success() { if !exit_status.success() {
eprintln!("init: {command:?} failed with {exit_status}"); crate::dbgprintln!("init: {command:?} failed with {exit_status}");
} }
} }
Err(err) => { Err(err) => {
eprintln!("init: failed to wait for {:?}: {}", command, err) crate::dbgprintln!("init: failed to wait for {:?}: {}", command, err)
} }
} }
} }
+1
View File
@@ -131,6 +131,7 @@ fn true_bool() -> bool {
true true
} }
#[derive(Debug)]
pub enum UnitKind { pub enum UnitKind {
LegacyScript { script: Vec<Command> }, LegacyScript { script: Vec<Command> },
Service { service: Service }, Service { service: Service },