1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
use std::{
mem,
sync::atomic::{AtomicBool, Ordering},
};
use parking_lot::Mutex;
/// Register a `FnOnce(&ProcessStartArgs)` closure to be called on [`init!`].
///
/// Components that spawn special process instances implemented on the same executable
/// can use this macro to inject their own "main" without needing to ask the user to plug an init
/// function on the executable main. The component can spawn an instance of the current executable
/// with marker environment variables that identify the component's process.
///
/// [`init!`]: crate::init!
///
/// # Examples
///
/// The example below declares a "main" for a foo component and a function that spawns it.
///
/// ```
/// zng_env::on_process_start!(|_| {
/// if std::env::var("FOO_MARKER").is_ok() {
/// println!("Spawned as foo!");
/// zng_env::exit(0);
/// }
/// });
///
/// fn main() {
/// zng_env::init!(); // foo_main OR
/// // normal main
/// }
///
/// pub fn spawn_foo() -> std::io::Result<()> {
/// std::process::Command::new(std::env::current_exe()?).env("FOO_MARKER", "").spawn()?;
/// Ok(())
/// }
/// ```
///
/// Note the use of [`exit`], it is important to call it to collaborate with [`on_process_exit`] handlers.
///
/// # App Context
///
/// This event happens on the executable process context, before any `APP` context starts, you can use
/// `zng::app::on_app_start` here to register a handler to be called in the app context, if and when it starts.
///
/// # Web Assembly
///
/// Crates that declare `on_process_start` must have the [`wasm_bindgen`] dependency to compile for the `wasm32` target.
///
/// In `Cargo.toml` add this dependency:
///
/// ```toml
/// [target.'cfg(target_arch = "wasm32")'.dependencies]
/// wasm-bindgen = "*"
/// ```
///
/// Try to match the version used by `zng-env`.
///
/// # Linker Optimizer Issues
///
/// The macOS system linker can "optimize" away crates that are only referenced via this macro, that is, a crate dependency
/// that is not otherwise directly addressed by code. To workaround this issue you can add a bogus reference to the crate code, something
/// that is not trivial to optimize away. Unfortunately this code must be added on the dependent crate, or on an intermediary dependency,
/// if your crate is at risk of being used this way please document this issue.
///
/// See [`zng#437`] for an example of how to fix this issue.
///
/// [`wasm_bindgen`]: https://crates.io/crates/wasm-bindgen
/// [`zng#437`]: https://github.com/zng-ui/zng/pull/437
#[macro_export]
macro_rules! on_process_start {
($closure:expr) => {
$crate::__on_process_start! {$closure}
};
}
#[cfg(not(target_arch = "wasm32"))]
#[doc(hidden)]
#[macro_export]
macro_rules! __on_process_start {
($closure:expr) => {
// expanded from:
// #[linkme::distributed_slice(ZNG_ENV_ON_PROCESS_START)]
// static _ON_PROCESS_START: fn...;
// so that users don't need to depend on linkme just to call this macro.
#[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)
}
};
}
#[cfg(target_arch = "wasm32")]
#[doc(hidden)]
#[macro_export]
macro_rules! __on_process_start {
($closure:expr) => {
$crate::wasm_process_start! {$crate,$closure}
};
}
#[doc(hidden)]
#[cfg(target_arch = "wasm32")]
pub use wasm_bindgen::prelude::wasm_bindgen;
#[doc(hidden)]
#[cfg(target_arch = "wasm32")]
pub use zng_env_proc_macros::wasm_process_start;
#[cfg(target_arch = "wasm32")]
std::thread_local! {
#[doc(hidden)]
pub static WASM_INIT: std::cell::RefCell<Vec<fn(&ProcessStartArgs)>> = const { std::cell::RefCell::new(vec![]) };
}
#[cfg(not(target_arch = "wasm32"))]
#[doc(hidden)]
#[linkme::distributed_slice]
pub static ZNG_ENV_ON_PROCESS_START: [fn(&ProcessStartArgs)];
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn process_init() -> impl Drop {
process_init_impl(&ZNG_ENV_ON_PROCESS_START)
}
fn process_init_impl(handlers: &[fn(&ProcessStartArgs)]) -> MainExitHandler {
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 = handlers.len();
for h in handlers {
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
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn process_init() -> impl Drop {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
let window = web_sys::window().expect("cannot 'init!', no window object");
let module = js_sys::Reflect::get(&window, &"__zng_env_init_module".into())
.expect("cannot 'init!', missing module in 'window.__zng_env_init_module'");
if module == wasm_bindgen::JsValue::undefined() || module == wasm_bindgen::JsValue::null() {
panic!("cannot 'init!', missing module in 'window.__zng_env_init_module'");
}
let module: js_sys::Object = module.into();
for entry in js_sys::Object::entries(&module) {
let entry: js_sys::Array = entry.into();
let ident = entry.get(0).as_string().expect("expected ident at entry[0]");
if ident.starts_with("__zng_env_start_") {
let func: js_sys::Function = entry.get(1).into();
if let Err(e) = func.call0(&wasm_bindgen::JsValue::NULL) {
panic!("'init!' function error, {e:?}");
}
}
}
process_init_impl(&WASM_INIT.with_borrow_mut(std::mem::take))
}
/// Arguments for [`on_process_start`] handlers.
///
/// Empty in this release.
pub struct ProcessStartArgs {
/// Number of start handlers yet to run.
pub next_handlers_count: usize,
/// Number of times this handler has yielded.
///
/// If this exceeds 32 times the handler is ignored.
pub yield_count: u16,
yield_requested: AtomicBool,
}
impl ProcessStartArgs {
/// Yield requests after this are ignored.
pub const MAX_YIELD_COUNT: u16 = 32;
/// Let other process start handlers run first.
///
/// The handler must call this if it takes over the process and it cannot determinate if it should from the environment.
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>;
zng_unique_id::hot_static! {
static ON_PROCESS_EXIT: Mutex<Vec<ExitHandler>> = Mutex::new(vec![]);
}
/// Terminates the current process with the specified exit code.
///
/// This function must be used instead of `std::process::exit` as it runs the [`on_process_exit`].
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);
}
}
/// Arguments for [`on_process_exit`] handlers.
#[non_exhaustive]
pub struct ProcessExitArgs {
/// Exit code that will be used.
pub code: i32,
}
/// Register a `handler` to run once when the current process exits.
///
/// Note that the handler is only called if the process is terminated by [`exit`], or by the executable main
/// function returning if [`init!`] is called on it.
///
/// [`init!`]: crate::init!
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))
}
/// Defines the state of the current process instance.
///
/// Use [`process_lifetime_state()`] to get.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessLifetimeState {
/// Init not called yet.
BeforeInit,
/// Init called and the function where it is called has not returned yet.
Inited,
/// Init called and the function where it is called is returning.
Exiting,
}
zng_unique_id::hot_static! {
static PROCESS_LIFETIME_STATE: Mutex<ProcessLifetimeState> = Mutex::new(ProcessLifetimeState::BeforeInit);
}
/// Get the state of the current process instance.
pub fn process_lifetime_state() -> ProcessLifetimeState {
*zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock()
}
/// Panics with an standard message if `zng::env::init!()` was not called or was not called correctly.
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")
}
}
}