1use std::{
2 mem,
3 sync::atomic::{AtomicU8, Ordering},
4};
5
6use parking_lot::Mutex;
7
8#[doc(hidden)]
9#[cfg(not(target_arch = "wasm32"))]
10pub use linkme as __linkme;
11
12#[macro_export]
84macro_rules! on_process_start {
85 ($closure:expr) => {
86 $crate::__on_process_start! {$closure}
87 };
88}
89
90#[cfg(not(target_arch = "wasm32"))]
91#[doc(hidden)]
92#[macro_export]
93macro_rules! __on_process_start {
94 ($closure:expr) => {
95 const _: () = {
96 #[$crate::__linkme::distributed_slice($crate::ZNG_ENV_ON_PROCESS_START)]
97 #[linkme(crate = $crate::__linkme)]
98 #[doc(hidden)]
99 static _ON_PROCESS_START: fn(&$crate::ProcessStartArgs) = _on_process_start;
100 #[doc(hidden)]
101 fn _on_process_start(args: &$crate::ProcessStartArgs) {
102 fn on_process_start(args: &$crate::ProcessStartArgs, handler: impl FnOnce(&$crate::ProcessStartArgs)) {
103 handler(args)
104 }
105 on_process_start(args, $closure)
106 }
107 };
108 };
109}
110
111#[cfg(target_arch = "wasm32")]
112#[doc(hidden)]
113#[macro_export]
114macro_rules! __on_process_start {
115 ($closure:expr) => {
116 $crate::wasm_process_start! {$crate,$closure}
117 };
118}
119
120#[doc(hidden)]
121#[cfg(target_arch = "wasm32")]
122pub use wasm_bindgen::prelude::wasm_bindgen;
123
124#[doc(hidden)]
125#[cfg(target_arch = "wasm32")]
126pub use zng_env_proc_macros::wasm_process_start;
127use zng_txt::Txt;
128
129#[cfg(target_arch = "wasm32")]
130std::thread_local! {
131 #[doc(hidden)]
132 pub static WASM_INIT: std::cell::RefCell<Vec<fn(&ProcessStartArgs)>> = const { std::cell::RefCell::new(vec![]) };
133}
134
135#[cfg(not(target_arch = "wasm32"))]
136#[doc(hidden)]
137#[linkme::distributed_slice]
138pub static ZNG_ENV_ON_PROCESS_START: [fn(&ProcessStartArgs)];
139
140#[cfg(not(target_arch = "wasm32"))]
141pub(crate) fn process_init() -> impl Drop {
142 process_init_impl(&ZNG_ENV_ON_PROCESS_START)
143}
144
145fn process_init_impl(handlers: &[fn(&ProcessStartArgs)]) -> MainExitHandler {
146 let process_state = std::mem::replace(
147 &mut *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock(),
148 ProcessLifetimeState::Inited,
149 );
150 assert_eq!(process_state, ProcessLifetimeState::BeforeInit, "init!() already called");
151
152 let mut yielded = vec![];
153 let mut yield_until_app = vec![];
155 let mut next_handlers_count = handlers.len();
156 for h in handlers {
157 next_handlers_count -= 1;
158 let args = ProcessStartArgs {
159 next_handlers_count,
160 yield_count: 0,
161 yield_requested: AtomicU8::new(0),
162 };
163 h(&args);
164 match args.yield_requested.load(Ordering::Relaxed) {
165 ProcessStartArgs::YIELD_ONCE => {
166 yielded.push(h);
167 next_handlers_count += 1;
168 }
169 ProcessStartArgs::YIELD_UNTIL_APP => {
170 yield_until_app.push(h);
171 }
172 _ => {}
173 }
174 }
175
176 let mut yield_count = 0;
177 while !yielded.is_empty() {
178 yield_count += 1;
179 if yield_count > ProcessStartArgs::MAX_YIELD_COUNT {
180 eprintln!("start handlers requested `yield_start` more them 32 times");
181 break;
182 }
183
184 next_handlers_count = yielded.len();
185 for h in mem::take(&mut yielded) {
186 next_handlers_count -= 1;
187 let args = ProcessStartArgs {
188 next_handlers_count,
189 yield_count,
190 yield_requested: AtomicU8::new(0),
191 };
192 h(&args);
193 match args.yield_requested.load(Ordering::Relaxed) {
194 ProcessStartArgs::YIELD_ONCE => {
195 yielded.push(h);
196 next_handlers_count += 1;
197 }
198 ProcessStartArgs::YIELD_UNTIL_APP => {
199 yield_until_app.push(h);
200 }
201 _ => {}
202 }
203 }
204 }
205
206 for h in yield_until_app {
207 let args = ProcessStartArgs {
208 next_handlers_count: 0,
209 yield_count: ProcessStartArgs::MAX_YIELD_COUNT,
210 yield_requested: AtomicU8::new(0),
211 };
212 h(&args);
213 if args.yield_requested.load(Ordering::Relaxed) != 0 {
214 eprintln!("handler requested `yield_until_app` and then yielded again")
215 }
216 }
217 MainExitHandler
218}
219
220#[cfg(target_arch = "wasm32")]
221pub(crate) fn process_init() -> impl Drop {
222 std::panic::set_hook(Box::new(console_error_panic_hook::hook));
223
224 let window = web_sys::window().expect("cannot 'init!', no window object");
225 let module = js_sys::Reflect::get(&window, &"__zng_env_init_module".into())
226 .expect("cannot 'init!', missing module in 'window.__zng_env_init_module'");
227
228 if module == wasm_bindgen::JsValue::undefined() || module == wasm_bindgen::JsValue::null() {
229 panic!("cannot 'init!', missing module in 'window.__zng_env_init_module'");
230 }
231
232 let module: js_sys::Object = module.into();
233
234 for entry in js_sys::Object::entries(&module) {
235 let entry: js_sys::Array = entry.into();
236 let ident = entry.get(0).as_string().expect("expected ident at entry[0]");
237
238 if ident.starts_with("__zng_env_start_") {
239 let func: js_sys::Function = entry.get(1).into();
240 if let Err(e) = func.call0(&wasm_bindgen::JsValue::NULL) {
241 panic!("'init!' function error, {e:?}");
242 }
243 }
244 }
245
246 process_init_impl(&WASM_INIT.with_borrow_mut(std::mem::take))
247}
248
249pub struct ProcessStartArgs {
253 pub next_handlers_count: usize,
255
256 pub yield_count: u16,
260
261 yield_requested: AtomicU8,
262}
263impl ProcessStartArgs {
264 pub const MAX_YIELD_COUNT: u16 = 32;
266
267 const YIELD_ONCE: u8 = 1;
268 const YIELD_UNTIL_APP: u8 = 2;
269
270 pub fn yield_once(&self) {
291 self.yield_requested.store(Self::YIELD_ONCE, Ordering::Relaxed);
292 }
293
294 #[deprecated = "use `APP.on_init` to register a closure that runs if the process becomes an app process"]
299 pub fn yield_until_app(&self) -> bool {
300 if self.next_handlers_count > 0 && self.yield_count < Self::MAX_YIELD_COUNT {
301 self.yield_requested.store(Self::YIELD_UNTIL_APP, Ordering::Relaxed);
303 return true;
304 }
305
306 crate::init_process_name("app-process");
308
309 false
310 }
311}
312
313struct MainExitHandler;
314impl Drop for MainExitHandler {
315 fn drop(&mut self) {
316 run_exit_handlers(if std::thread::panicking() { 101 } else { 0 })
317 }
318}
319
320type ExitHandler = Box<dyn FnOnce(&ProcessExitArgs) + Send + 'static>;
321
322zng_unique_id::hot_static! {
323 static ON_PROCESS_EXIT: Mutex<Vec<ExitHandler>> = Mutex::new(vec![]);
324}
325
326pub fn exit(code: i32) -> ! {
330 run_exit_handlers(code);
331 std::process::exit(code)
332}
333
334fn run_exit_handlers(code: i32) {
335 *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock() = ProcessLifetimeState::Exiting;
336
337 let on_exit = mem::take(&mut *zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock());
338 let args = ProcessExitArgs { code };
339 for h in on_exit {
340 h(&args);
341 }
342}
343
344#[non_exhaustive]
346pub struct ProcessExitArgs {
347 pub code: i32,
349}
350
351pub fn on_process_exit(handler: impl FnOnce(&ProcessExitArgs) + Send + 'static) {
358 zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock().push(Box::new(handler))
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum ProcessLifetimeState {
366 BeforeInit,
368 Inited,
370 Exiting,
372}
373
374zng_unique_id::hot_static! {
375 static PROCESS_LIFETIME_STATE: Mutex<ProcessLifetimeState> = Mutex::new(ProcessLifetimeState::BeforeInit);
376}
377zng_unique_id::hot_static! {
378 static PROCESS_NAME: Mutex<Txt> = Mutex::new(Txt::from_static(""));
379}
380
381pub fn process_lifetime_state() -> ProcessLifetimeState {
383 *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock()
384}
385
386pub fn process_name() -> Txt {
401 zng_unique_id::hot_static_ref!(PROCESS_NAME).lock().clone()
402}
403
404pub fn set_process_name(name: impl Into<Txt>) {
412 set_process_name_impl(name.into(), true);
413}
414
415pub fn init_process_name(name: impl Into<Txt>) -> bool {
421 set_process_name_impl(name.into(), false)
422}
423
424fn set_process_name_impl(new_name: Txt, replace: bool) -> bool {
425 let mut name = zng_unique_id::hot_static_ref!(PROCESS_NAME).lock();
426 if replace || name.is_empty() {
427 *name = new_name;
428 drop(name);
429 tracing::info!("pid: {}, name: {}", std::process::id(), process_name());
430 true
431 } else {
432 false
433 }
434}
435
436pub fn assert_inited() {
438 match process_lifetime_state() {
439 ProcessLifetimeState::BeforeInit => panic!("env not inited, please call `zng::env::init!()` in main"),
440 ProcessLifetimeState::Inited => {}
441 ProcessLifetimeState::Exiting => {
442 panic!("env not inited correctly, please call `zng::env::init!()` at the beginning of the actual main function")
443 }
444 }
445}