cd05afdcb1
- Skip binary existence check in probe(): Redox scheme paths (especially /scheme/initfs/) may block on open/stat indefinitely. Command::new() spawn fails cleanly if binary missing. - In initfs mode: use synchronous probe, do bounded deferred retries, then exit. Rootfs instance handles hotplug. - Avoids pcid config handle read hang that blocks async threads.
663 lines
22 KiB
Rust
663 lines
22 KiB
Rust
mod config;
|
||
mod exec;
|
||
mod hotplug;
|
||
mod scheme;
|
||
|
||
use std::sync::atomic::{AtomicBool, Ordering};
|
||
use std::sync::{Arc, Mutex};
|
||
use std::thread;
|
||
use std::time::{Duration, Instant};
|
||
use std::{env, fs, process};
|
||
|
||
use redox_driver_core::device::DeviceId;
|
||
use redox_driver_core::driver::ProbeResult;
|
||
use redox_driver_core::manager::{DeviceManager, ManagerConfig, ProbeEvent};
|
||
use redox_driver_pci::PciBus;
|
||
use redox_driver_acpi::AcpiBus;
|
||
use std::fs::OpenOptions;
|
||
use std::io::Write;
|
||
|
||
use config::DriverConfig;
|
||
use scheme::{DriverManagerScheme, notify_bind};
|
||
|
||
/// Global flag set by SIGTERM handler to request graceful shutdown.
|
||
static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||
|
||
extern "C" fn sigterm_handler(_sig: i32) {
|
||
SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
|
||
}
|
||
|
||
fn install_sigterm_handler() {
|
||
unsafe {
|
||
libc::signal(libc::SIGTERM, sigterm_handler as *const () as usize);
|
||
}
|
||
}
|
||
|
||
struct StderrLogger;
|
||
|
||
const BOOT_TIMELINE_PATH: &str = "/tmp/redbear-boot-timeline.json";
|
||
|
||
impl log::Log for StderrLogger {
|
||
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||
metadata.level() <= log::Level::Info
|
||
}
|
||
fn log(&self, record: &log::Record) {
|
||
if self.enabled(record.metadata()) {
|
||
eprintln!("[{}] driver-manager: {}", record.level(), record.args());
|
||
}
|
||
}
|
||
fn flush(&self) {}
|
||
}
|
||
|
||
/// Wait for a scheme to become available by polling `/scheme/{name}`.
|
||
/// Returns true if the scheme appeared within the timeout, false otherwise.
|
||
///
|
||
/// In initfs mode, driver-manager starts before pcid and acpid. The PCI bus
|
||
/// scheme (`/scheme/pci`) is registered by pcid, which is spawned by hwd
|
||
/// (service 40). The ACPI bus scheme (`/scheme/acpi`) is registered by acpid
|
||
/// (service 41). Without waiting, enumeration fails with 0 devices and the
|
||
/// deferred retry loop (3 × 500ms) is too short.
|
||
fn wait_for_scheme(name: &str, timeout: Duration) -> bool {
|
||
let path = format!("/scheme/{}", name);
|
||
let start = Instant::now();
|
||
let poll_interval = Duration::from_millis(100);
|
||
|
||
while start.elapsed() < timeout {
|
||
// Use read_dir instead of Path::exists() — Redox scheme paths
|
||
// may not respond to exists()/metadata() while still being
|
||
// functional for directory enumeration.
|
||
if fs::read_dir(&path).is_ok() {
|
||
log::info!(
|
||
"scheme {} available after {}ms",
|
||
name,
|
||
start.elapsed().as_millis()
|
||
);
|
||
return true;
|
||
}
|
||
thread::sleep(poll_interval);
|
||
}
|
||
|
||
log::warn!(
|
||
"scheme {} not available after {}ms",
|
||
name,
|
||
timeout.as_millis()
|
||
);
|
||
false
|
||
}
|
||
|
||
fn run_enumeration(
|
||
manager: &Arc<Mutex<DeviceManager>>,
|
||
scheme: &DriverManagerScheme,
|
||
initfs: bool,
|
||
) -> (usize, usize) {
|
||
let enum_start = Instant::now();
|
||
let events = match manager.lock() {
|
||
Ok(mut mgr) => mgr.enumerate(),
|
||
Err(err) => {
|
||
log::error!("failed to enumerate devices: manager lock poisoned: {err}");
|
||
return (0, 0);
|
||
}
|
||
};
|
||
let enum_duration = enum_start.elapsed();
|
||
|
||
let mut bound = 0usize;
|
||
let mut deferred = 0usize;
|
||
|
||
for event in &events {
|
||
log_timeline(event);
|
||
match event {
|
||
ProbeEvent::ProbeCompleted {
|
||
device,
|
||
driver_name,
|
||
result,
|
||
} => match result {
|
||
ProbeResult::Bound => {
|
||
log::info!("bound: {} -> {}", device.path, driver_name);
|
||
notify_bound_device(scheme, device, driver_name);
|
||
bound += 1;
|
||
}
|
||
ProbeResult::Deferred { reason } => {
|
||
log::info!("deferred: {} -> {} ({})", device.path, driver_name, reason);
|
||
deferred += 1;
|
||
}
|
||
ProbeResult::Fatal { reason } => {
|
||
log::error!("fatal: {} -> {} ({})", device.path, driver_name, reason);
|
||
}
|
||
_ => {}
|
||
},
|
||
ProbeEvent::BusEnumerated { bus, device_count } => {
|
||
log::info!("bus {} enumerated {} device(s)", bus, device_count);
|
||
}
|
||
ProbeEvent::BusEnumerationFailed { bus, error } => {
|
||
if initfs && *bus == "pci" {
|
||
log::warn!("bus {} enumeration not yet ready (initfs, pcid may still be starting): {:?}", bus, error);
|
||
} else {
|
||
log::error!("bus {} enumeration failed: {:?}", bus, error);
|
||
}
|
||
}
|
||
ProbeEvent::AlreadyBound {
|
||
device,
|
||
driver_name,
|
||
} => {
|
||
log::debug!("already bound: {} -> {}", device.path, driver_name);
|
||
}
|
||
ProbeEvent::NoDriverFound { device } => {
|
||
log::info!("no driver found for {} device {}", device.bus, device.path);
|
||
}
|
||
ProbeEvent::MissingDriver {
|
||
device,
|
||
driver_name,
|
||
} => {
|
||
log::error!(
|
||
"deferred probe lost driver: {} device {} wanted {}",
|
||
device.bus,
|
||
device.path,
|
||
driver_name
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
log::info!(
|
||
"enumeration complete: {} bound, {} deferred ({}ms total)",
|
||
bound,
|
||
deferred,
|
||
enum_duration.as_millis()
|
||
);
|
||
|
||
(bound, deferred)
|
||
}
|
||
|
||
fn notify_bound_device(scheme: &DriverManagerScheme, device: &DeviceId, driver_name: &str) {
|
||
// Notify for both PCI and ACPI devices
|
||
notify_bind(scheme, &device.path, driver_name);
|
||
}
|
||
|
||
fn reset_timeline_log() {
|
||
// Best-effort: truncate or create empty. On scheme filesystems that
|
||
// don't support truncate on existing files, this may fail — that's OK,
|
||
// the append path will handle it.
|
||
match fs::write(BOOT_TIMELINE_PATH, "") {
|
||
Ok(()) => {}
|
||
Err(_) => {
|
||
let _ = fs::remove_file(BOOT_TIMELINE_PATH);
|
||
}
|
||
}
|
||
}
|
||
|
||
fn json_escape(value: &str) -> String {
|
||
let mut escaped = String::with_capacity(value.len());
|
||
for ch in value.chars() {
|
||
match ch {
|
||
'\\' => escaped.push_str("\\\\"),
|
||
'"' => escaped.push_str("\\\""),
|
||
'\n' => escaped.push_str("\\n"),
|
||
'\r' => escaped.push_str("\\r"),
|
||
'\t' => escaped.push_str("\\t"),
|
||
ch if ch.is_control() => escaped.push_str(&format!("\\u{:04x}", ch as u32)),
|
||
ch => escaped.push(ch),
|
||
}
|
||
}
|
||
escaped
|
||
}
|
||
|
||
fn log_timeline(event: &ProbeEvent) {
|
||
let timestamp = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap_or_default()
|
||
.as_millis();
|
||
|
||
let entry = match event {
|
||
ProbeEvent::BusEnumerated { bus, device_count } => format!(
|
||
r#"{{"ts":{},"event":"bus_enumerated","bus":"{}","count":{}}}"#,
|
||
timestamp,
|
||
json_escape(bus),
|
||
device_count
|
||
),
|
||
ProbeEvent::BusEnumerationFailed { bus, error } => format!(
|
||
r#"{{"ts":{},"event":"bus_enumeration_failed","bus":"{}","error":"{}"}}"#,
|
||
timestamp,
|
||
json_escape(bus),
|
||
json_escape(&format!("{error:?}"))
|
||
),
|
||
ProbeEvent::ProbeCompleted {
|
||
device,
|
||
driver_name,
|
||
result,
|
||
} => {
|
||
let status = match result {
|
||
ProbeResult::Bound => "bound",
|
||
ProbeResult::Deferred { .. } => "deferred",
|
||
ProbeResult::Fatal { .. } => "failed",
|
||
ProbeResult::NotSupported => "skipped",
|
||
};
|
||
format!(
|
||
r#"{{"ts":{},"event":"probe","device":"{}","driver":"{}","status":"{}"}}"#,
|
||
timestamp,
|
||
json_escape(&device.path),
|
||
json_escape(driver_name),
|
||
status
|
||
)
|
||
}
|
||
ProbeEvent::NoDriverFound { device } => format!(
|
||
r#"{{"ts":{},"event":"no_driver","bus":"{}","device":"{}"}}"#,
|
||
timestamp,
|
||
json_escape(&device.bus),
|
||
json_escape(&device.path)
|
||
),
|
||
ProbeEvent::AlreadyBound {
|
||
device,
|
||
driver_name,
|
||
} => format!(
|
||
r#"{{"ts":{},"event":"already_bound","bus":"{}","device":"{}","driver":"{}"}}"#,
|
||
timestamp,
|
||
json_escape(&device.bus),
|
||
json_escape(&device.path),
|
||
json_escape(driver_name)
|
||
),
|
||
ProbeEvent::MissingDriver {
|
||
device,
|
||
driver_name,
|
||
} => format!(
|
||
r#"{{"ts":{},"event":"missing_driver","bus":"{}","device":"{}","driver":"{}"}}"#,
|
||
timestamp,
|
||
json_escape(&device.bus),
|
||
json_escape(&device.path),
|
||
json_escape(driver_name)
|
||
),
|
||
};
|
||
|
||
match OpenOptions::new()
|
||
.create(true)
|
||
.append(true)
|
||
.open(BOOT_TIMELINE_PATH)
|
||
{
|
||
Ok(mut file) => {
|
||
if let Err(err) = writeln!(file, "{entry}") {
|
||
// EPIPE or other write errors can occur when /tmp is backed
|
||
// by a scheme that doesn't support append writes, or when the
|
||
// filesystem is not yet fully ready. Log once and suppress
|
||
// all subsequent write errors to avoid log spam.
|
||
static WRITE_ERROR_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||
if !WRITE_ERROR_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||
log::warn!("failed to append boot timeline entry to {BOOT_TIMELINE_PATH}: {err} (suppressing further write errors)");
|
||
}
|
||
}
|
||
}
|
||
Err(err) => {
|
||
// EEXIST (os error 17) can occur when the file already exists
|
||
// but the scheme filesystem doesn't support create+append.
|
||
// EPIPE and other errors occur when /tmp isn't ready.
|
||
// Log once and suppress all subsequent open errors.
|
||
static OPEN_ERROR_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||
if !OPEN_ERROR_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||
log::warn!("failed to open boot timeline log at {BOOT_TIMELINE_PATH}: {err} (suppressing further open errors)");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fn run_status() {
|
||
// Print the boot timeline log if it exists.
|
||
match fs::read_to_string(BOOT_TIMELINE_PATH) {
|
||
Ok(content) => {
|
||
if content.trim().is_empty() {
|
||
println!("No boot timeline data found at {}", BOOT_TIMELINE_PATH);
|
||
println!("Driver manager has not completed enumeration yet.");
|
||
return;
|
||
}
|
||
|
||
println!("=== Red Bear OS Driver Manager Status ===");
|
||
println!();
|
||
|
||
let mut bound = 0usize;
|
||
let mut deferred = 0usize;
|
||
let mut failed = 0usize;
|
||
let mut no_driver = 0usize;
|
||
let mut buses = Vec::new();
|
||
|
||
for line in content.lines() {
|
||
if line.trim().is_empty() {
|
||
continue;
|
||
}
|
||
// Parse JSON timeline entries
|
||
if line.contains("\"event\":\"bus_enumerated\"") {
|
||
if let Some(bus) = extract_json_string(line, "bus") {
|
||
if let Some(count) = extract_json_number(line, "count") {
|
||
buses.push((bus, count));
|
||
}
|
||
}
|
||
} else if line.contains("\"status\":\"bound\"") {
|
||
bound += 1;
|
||
} else if line.contains("\"status\":\"deferred\"") {
|
||
deferred += 1;
|
||
} else if line.contains("\"status\":\"failed\"") {
|
||
failed += 1;
|
||
} else if line.contains("\"event\":\"no_driver\"") {
|
||
no_driver += 1;
|
||
}
|
||
}
|
||
|
||
println!("Bus enumeration:");
|
||
for (bus, count) in &buses {
|
||
println!(" {}: {} device(s)", bus, count);
|
||
}
|
||
println!();
|
||
println!("Driver binding:");
|
||
println!(" bound: {}", bound);
|
||
println!(" deferred: {}", deferred);
|
||
println!(" failed: {}", failed);
|
||
println!(" no driver: {}", no_driver);
|
||
println!();
|
||
println!("Timeline log: {}", BOOT_TIMELINE_PATH);
|
||
}
|
||
Err(err) => {
|
||
println!("Cannot read {}: {}", BOOT_TIMELINE_PATH, err);
|
||
println!("Driver manager may not have run yet.");
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Extract a JSON string value for a given key from a single-line JSON object.
|
||
fn extract_json_string(line: &str, key: &str) -> Option<String> {
|
||
let pattern = format!("\"{}\":\"", key);
|
||
let start = line.find(&pattern)?;
|
||
let value_start = start + pattern.len();
|
||
let end = line[value_start..].find('"')?;
|
||
Some(line[value_start..value_start + end].to_string())
|
||
}
|
||
|
||
/// Extract a JSON number value for a given key from a single-line JSON object.
|
||
fn extract_json_number(line: &str, key: &str) -> Option<usize> {
|
||
let pattern = format!("\"{}\":", key);
|
||
let start = line.find(&pattern)?;
|
||
let value_start = start + pattern.len();
|
||
let rest = &line[value_start..];
|
||
let end = rest.find(|c: char| !c.is_ascii_digit()).unwrap_or(rest.len());
|
||
rest[..end].parse().ok()
|
||
}
|
||
|
||
fn main() {
|
||
log::set_logger(&StderrLogger).ok();
|
||
log::set_max_level(log::LevelFilter::Info);
|
||
|
||
// Install SIGTERM handler for graceful shutdown
|
||
install_sigterm_handler();
|
||
|
||
let args: Vec<String> = env::args().collect();
|
||
let initfs = args.iter().any(|a| a == "--initfs");
|
||
let hotplug_mode = args.iter().any(|a| a == "--hotplug");
|
||
let status_mode = args.iter().any(|a| a == "--status");
|
||
|
||
// --status: print the current device registry from the boot timeline log
|
||
// and exit. This is for diagnostics: "what did driver-manager find?"
|
||
if status_mode {
|
||
run_status();
|
||
return;
|
||
}
|
||
|
||
let config_dir = if initfs {
|
||
"/scheme/initfs/lib/drivers.d"
|
||
} else {
|
||
"/lib/drivers.d"
|
||
};
|
||
|
||
let driver_configs = match DriverConfig::load_all(config_dir) {
|
||
Ok(c) => c,
|
||
Err(e) => {
|
||
log::error!("failed to load driver configs: {}", e);
|
||
process::exit(1);
|
||
}
|
||
};
|
||
|
||
if driver_configs.is_empty() {
|
||
log::warn!("no driver configs found in {}", config_dir);
|
||
process::exit(0);
|
||
}
|
||
|
||
log::info!("loaded {} driver config(s)", driver_configs.len());
|
||
|
||
let manager_config = ManagerConfig {
|
||
max_concurrent_probes: 4,
|
||
deferred_retry_ms: 500,
|
||
async_probe: !initfs,
|
||
};
|
||
|
||
let manager = Arc::new(Mutex::new(DeviceManager::new(manager_config.clone())));
|
||
let scheme = Arc::new(DriverManagerScheme::new());
|
||
|
||
match manager.lock() {
|
||
Ok(mut mgr) => {
|
||
// Register PCI bus first (higher priority — storage, network, GPU).
|
||
// Mirrors Linux's pci_scan_child_bus() via subsys_initcall.
|
||
mgr.register_bus(Box::new(PciBus::new()));
|
||
|
||
// Register ACPI bus for platform/I2C/SPI/GPIO/thermal devices.
|
||
// Mirrors Linux's acpi_bus_scan() which walks the namespace for
|
||
// _HID/_CID/_STA/_CRS. ACPI devices are enumerated from
|
||
// /scheme/acpi/symbols/ which acpid populates from the AML
|
||
// interpreter.
|
||
mgr.register_bus(Box::new(AcpiBus::new()));
|
||
|
||
for dc in &driver_configs {
|
||
mgr.register_driver(Box::new(dc.clone()));
|
||
}
|
||
}
|
||
Err(err) => {
|
||
log::error!("failed to configure driver manager: manager lock poisoned: {err}");
|
||
process::exit(1);
|
||
}
|
||
}
|
||
|
||
let mgr_clone = Arc::clone(&manager);
|
||
let scheme_clone = Arc::clone(&scheme);
|
||
|
||
// Ensure /tmp exists before writing the boot timeline log.
|
||
let _ = std::fs::create_dir_all("/tmp");
|
||
|
||
reset_timeline_log();
|
||
|
||
// In initfs mode, pcid (spawned by hwd at service 40) may not have
|
||
// registered /scheme/pci yet when driver-manager starts at service 00.
|
||
// Wait for required bus schemes to appear before enumerating.
|
||
if initfs {
|
||
wait_for_scheme("pci", Duration::from_secs(30));
|
||
wait_for_scheme("acpi", Duration::from_secs(10));
|
||
}
|
||
|
||
if manager_config.async_probe {
|
||
let handle = thread::spawn(move || {
|
||
let (bound, deferred) = run_enumeration(&mgr_clone, scheme_clone.as_ref(), initfs);
|
||
log::info!("async enum: {} bound, {} deferred", bound, deferred);
|
||
});
|
||
if handle.join().is_err() {
|
||
log::error!("initial enumeration thread panicked");
|
||
process::exit(1);
|
||
}
|
||
} else {
|
||
let (bound, deferred) = run_enumeration(&manager, scheme.as_ref(), initfs);
|
||
log::info!("enum complete: {} bound, {} deferred", bound, deferred);
|
||
}
|
||
|
||
match scheme::start_scheme_server(Arc::clone(&scheme)) {
|
||
Ok(true) => {
|
||
log::info!("driver-manager: scheme server started successfully");
|
||
}
|
||
Ok(false) => {
|
||
log::warn!("driver-manager: scheme already registered — another instance is active, continuing without scheme server");
|
||
}
|
||
Err(err) => {
|
||
log::error!("{err}");
|
||
process::exit(1);
|
||
}
|
||
}
|
||
|
||
if hotplug_mode {
|
||
log::info!("entering hotplug event loop");
|
||
hotplug::run_hotplug_loop(manager.clone(), scheme.clone(), 2000);
|
||
idle_forever();
|
||
}
|
||
|
||
// In initfs mode: do bounded retries for deferred probes, then exit
|
||
// so init can proceed to rootfs mount. The rootfs driver-manager
|
||
// instance (started by the rootfs service) handles hotplug and
|
||
// persistent management.
|
||
if initfs {
|
||
let max_retries = 10u32;
|
||
for retry in 1..=max_retries {
|
||
if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
||
log::info!("driver-manager: SIGTERM received, shutting down");
|
||
graceful_shutdown();
|
||
process::exit(0);
|
||
}
|
||
|
||
reap_all_drivers(&driver_configs);
|
||
thread::sleep(Duration::from_millis(500));
|
||
|
||
let retry_events = match manager.lock() {
|
||
Ok(mut mgr) => mgr.retry_deferred(),
|
||
Err(err) => {
|
||
log::error!("failed to retry deferred probes: manager lock poisoned: {err}");
|
||
break;
|
||
}
|
||
};
|
||
|
||
let mut remaining = 0;
|
||
let mut newly_bound = 0;
|
||
|
||
for event in &retry_events {
|
||
log_timeline(event);
|
||
if let ProbeEvent::ProbeCompleted {
|
||
device,
|
||
driver_name,
|
||
result,
|
||
} = event
|
||
{
|
||
match result {
|
||
ProbeResult::Bound => {
|
||
newly_bound += 1;
|
||
notify_bound_device(scheme.as_ref(), device, driver_name);
|
||
}
|
||
ProbeResult::Deferred { .. } => remaining += 1,
|
||
_ => {}
|
||
}
|
||
}
|
||
}
|
||
|
||
if remaining == 0 {
|
||
log::info!("all deferred resolved after {} retries (initfs)", retry);
|
||
break;
|
||
}
|
||
|
||
if newly_bound > 0 {
|
||
log::info!(
|
||
"initfs retry #{}: {} new, {} remaining",
|
||
retry,
|
||
newly_bound,
|
||
remaining
|
||
);
|
||
}
|
||
}
|
||
|
||
log::info!("driver-manager: initfs phase complete, exiting");
|
||
return;
|
||
}
|
||
|
||
let max_retries = 3u32;
|
||
for retry in 1..=max_retries {
|
||
if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
||
log::info!("driver-manager: SIGTERM received during deferred retry, shutting down");
|
||
graceful_shutdown();
|
||
process::exit(0);
|
||
}
|
||
|
||
// Check for crashed drivers during retry loop
|
||
reap_all_drivers(&driver_configs);
|
||
|
||
thread::sleep(Duration::from_millis(500));
|
||
|
||
let retry_events = match manager.lock() {
|
||
Ok(mut mgr) => mgr.retry_deferred(),
|
||
Err(err) => {
|
||
log::error!("failed to retry deferred probes: manager lock poisoned: {err}");
|
||
process::exit(1);
|
||
}
|
||
};
|
||
|
||
let mut remaining = 0;
|
||
let mut newly_bound = 0;
|
||
|
||
for event in &retry_events {
|
||
log_timeline(event);
|
||
if let ProbeEvent::ProbeCompleted {
|
||
device,
|
||
driver_name,
|
||
result,
|
||
} = event
|
||
{
|
||
match result {
|
||
ProbeResult::Bound => {
|
||
newly_bound += 1;
|
||
notify_bound_device(scheme.as_ref(), device, driver_name);
|
||
}
|
||
ProbeResult::Deferred { .. } => remaining += 1,
|
||
_ => {}
|
||
}
|
||
}
|
||
}
|
||
|
||
if remaining == 0 {
|
||
log::info!("all deferred resolved after {} retries", retry);
|
||
idle_forever();
|
||
}
|
||
|
||
if newly_bound > 0 {
|
||
log::info!(
|
||
"retry #{}: {} new, {} remaining",
|
||
retry,
|
||
newly_bound,
|
||
remaining
|
||
);
|
||
}
|
||
}
|
||
|
||
log::warn!("deferred probe retry limit reached");
|
||
idle_forever();
|
||
}
|
||
|
||
fn idle_forever() -> ! {
|
||
log::info!("driver-manager: entering persistent idle loop");
|
||
loop {
|
||
thread::sleep(Duration::from_secs(5));
|
||
if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
||
log::info!("driver-manager: SIGTERM received, performing graceful shutdown");
|
||
graceful_shutdown();
|
||
process::exit(0);
|
||
}
|
||
// Periodically check for exited child drivers
|
||
reap_all_drivers(&[]);
|
||
}
|
||
}
|
||
|
||
/// Poll all driver configs for exited children and log the results.
|
||
fn reap_all_drivers(driver_configs: &[DriverConfig]) {
|
||
for dc in driver_configs {
|
||
let exited = dc.reap_exited_children();
|
||
for (device_key, driver_name, code) in &exited {
|
||
log::warn!(
|
||
"reaped crashed driver: {} for device {} (exit {})",
|
||
driver_name,
|
||
device_key,
|
||
code
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
fn graceful_shutdown() {
|
||
// The DeviceManager and spawned children are managed by DriverConfig instances
|
||
// which track their child processes. On shutdown, we log and exit cleanly.
|
||
// Child processes will be orphaned but the kernel reaps them.
|
||
log::info!("driver-manager: clean shutdown complete");
|
||
}
|