use std::{
mem,
sync::atomic::{AtomicBool, Ordering},
};
use parking_lot::Mutex;
#[macro_export]
macro_rules! on_process_start {
($closure:expr) => {
#[used]
#[cfg_attr(
any(
target_os = "none",
target_os = "linux",
target_os = "android",
target_os = "fuchsia",
target_os = "psp"
),
link_section = "linkme_ZNG_ENV_ON_PROCESS_START"
)]
#[cfg_attr(
any(target_os = "macos", target_os = "ios", target_os = "tvos"),
link_section = "__DATA,__linkme7nCnSSdn,regular,no_dead_strip"
)]
#[cfg_attr(target_os = "windows", link_section = ".linkme_ZNG_ENV_ON_PROCESS_START$b")]
#[cfg_attr(target_os = "illumos", link_section = "set_linkme_ZNG_ENV_ON_PROCESS_START")]
#[cfg_attr(
any(target_os = "freebsd", target_os = "openbsd"),
link_section = "linkme_ZNG_ENV_ON_PROCESS_START"
)]
#[doc(hidden)]
static _ON_PROCESS_START: fn(&$crate::ProcessStartArgs) = _on_process_start;
fn _on_process_start(args: &$crate::ProcessStartArgs) {
fn on_process_start(args: &$crate::ProcessStartArgs, handler: impl FnOnce(&$crate::ProcessStartArgs)) {
handler(args)
}
on_process_start(args, $closure)
}
};
}
#[doc(hidden)]
#[linkme::distributed_slice]
pub static ZNG_ENV_ON_PROCESS_START: [fn(&ProcessStartArgs)];
pub(crate) fn process_init() -> impl Drop {
let process_state = std::mem::replace(
&mut *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock(),
ProcessLifetimeState::Inited,
);
assert_eq!(process_state, ProcessLifetimeState::BeforeInit, "init!() already called");
let mut yielded = vec![];
let mut next_handlers_count = ZNG_ENV_ON_PROCESS_START.len();
for h in ZNG_ENV_ON_PROCESS_START {
next_handlers_count -= 1;
let args = ProcessStartArgs {
next_handlers_count,
yield_count: 0,
yield_requested: AtomicBool::new(false),
};
h(&args);
if args.yield_requested.load(Ordering::Relaxed) {
yielded.push(h);
next_handlers_count += 1;
}
}
let mut yield_count = 0;
while !yielded.is_empty() {
yield_count += 1;
if yield_count > ProcessStartArgs::MAX_YIELD_COUNT {
eprintln!("start handlers requested `yield_start` more them 32 times");
break;
}
next_handlers_count = yielded.len();
for h in mem::take(&mut yielded) {
next_handlers_count -= 1;
let args = ProcessStartArgs {
next_handlers_count,
yield_count,
yield_requested: AtomicBool::new(false),
};
h(&args);
if args.yield_requested.load(Ordering::Relaxed) {
yielded.push(h);
next_handlers_count += 1;
}
}
}
MainExitHandler
}
pub struct ProcessStartArgs {
pub next_handlers_count: usize,
pub yield_count: u16,
yield_requested: AtomicBool,
}
impl ProcessStartArgs {
pub const MAX_YIELD_COUNT: u16 = 32;
pub fn yield_once(&self) {
self.yield_requested.store(true, Ordering::Relaxed);
}
}
struct MainExitHandler;
impl Drop for MainExitHandler {
fn drop(&mut self) {
run_exit_handlers(if std::thread::panicking() { 101 } else { 0 })
}
}
type ExitHandler = Box<dyn FnOnce(&ProcessExitArgs) + Send + 'static>;
use super::*;
zng_unique_id::hot_static! {
static ON_PROCESS_EXIT: Mutex<Vec<ExitHandler>> = Mutex::new(vec![]);
}
pub fn exit(code: i32) -> ! {
run_exit_handlers(code);
std::process::exit(code)
}
fn run_exit_handlers(code: i32) {
*zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock() = ProcessLifetimeState::Exiting;
let on_exit = mem::take(&mut *zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock());
let args = ProcessExitArgs { code };
for h in on_exit {
h(&args);
}
}
#[non_exhaustive]
pub struct ProcessExitArgs {
pub code: i32,
}
pub fn on_process_exit(handler: impl FnOnce(&ProcessExitArgs) + Send + 'static) {
zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock().push(Box::new(handler))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessLifetimeState {
BeforeInit,
Inited,
Exiting,
}
zng_unique_id::hot_static! {
static PROCESS_LIFETIME_STATE: Mutex<ProcessLifetimeState> = Mutex::new(ProcessLifetimeState::BeforeInit);
}
pub fn process_lifetime_state() -> ProcessLifetimeState {
*zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock()
}
pub fn assert_inited() {
match process_lifetime_state() {
ProcessLifetimeState::BeforeInit => panic!("env not inited, please call `zng::env::init!()` in main"),
ProcessLifetimeState::Inited => {}
ProcessLifetimeState::Exiting => {
panic!("env not inited correctly, please call `zng::env::init!()` at the beginning of the actual main function")
}
}
}