use std::{collections::HashSet, process};
use crate::{error::Result, parent};
use log::debug;
use nix::unistd::{fork, ForkResult, Pid};
use signal_hook::{
consts::{SIGCHLD, SIGINT, SIGQUIT, SIGTERM},
iterator::Signals,
};
pub struct Server<T> {
num_processes: u32,
resource: Option<T>,
child_init: Box<dyn Fn(u32, T)>,
child_num: u32,
}
impl<T> Server<T> {
pub fn from_resource(resource: T, child_init: Box<dyn Fn(u32, T)>, num_processes: u32) -> Self {
Self {
num_processes,
resource: Some(resource),
child_init,
child_num: 0,
}
}
pub fn prefork(&mut self) -> Result<Option<HashSet<Pid>>> {
let mut pids = HashSet::new();
let mut is_parent = true;
for _ in 0..self.num_processes {
let pid = self.fork()?;
if let Some(pid) = pid {
pids.insert(pid);
} else {
is_parent = false;
break;
}
}
if is_parent {
Ok(Some(pids))
} else {
Ok(None)
}
}
pub fn fork(&mut self) -> Result<Option<Pid>> {
match unsafe { fork()? } {
ForkResult::Parent { child } => {
self.child_num += 1;
Ok(Some(child))
}
ForkResult::Child => {
if let Some(resource) = self.resource.take() {
(self.child_init)(self.child_num, resource);
} else {
unreachable!("fork resource is empty");
}
Ok(None)
}
}
}
}
pub fn run_parent(mut pids: HashSet<Pid>) -> Result<()> {
let parent_pid = process::id();
debug!("Handling signals in parent {parent_pid}");
let mut signals = Signals::new([SIGINT, SIGTERM, SIGQUIT, SIGCHLD])?;
for signal in signals.forever() {
match signal {
SIGCHLD => {
parent::wait_process(&mut pids);
if pids.is_empty() {
break;
}
}
_ => break,
}
}
parent::kill_all(pids)
}