1use std::{
2 mem,
3 sync::atomic::{AtomicBool, Ordering},
4};
5
6use parking_lot::Mutex;
7
8#[macro_export]
80macro_rules! on_process_start {
81 ($closure:expr) => {
82 $crate::__on_process_start! {$closure}
83 };
84}
85
86#[cfg(not(target_arch = "wasm32"))]
87#[doc(hidden)]
88#[macro_export]
89macro_rules! __on_process_start {
90 ($closure:expr) => {
91 #[used]
96 #[cfg_attr(
97 any(
98 target_os = "none",
99 target_os = "linux",
100 target_os = "android",
101 target_os = "fuchsia",
102 target_os = "psp"
103 ),
104 unsafe(link_section = "linkme_ZNG_ENV_ON_PROCESS_START")
105 )]
106 #[cfg_attr(
107 any(target_os = "macos", target_os = "ios", target_os = "tvos"),
108 unsafe(link_section = "__DATA,__linkme7nCnSSdn,regular,no_dead_strip")
109 )]
110 #[cfg_attr(
111 any(target_os = "uefi", target_os = "windows"),
112 unsafe(link_section = ".linkme_ZNG_ENV_ON_PROCESS_START$b")
113 )]
114 #[cfg_attr(target_os = "illumos", unsafe(link_section = "set_linkme_ZNG_ENV_ON_PROCESS_START"))]
115 #[cfg_attr(
116 any(target_os = "freebsd", target_os = "openbsd"),
117 unsafe(link_section = "linkme_ZNG_ENV_ON_PROCESS_START")
118 )]
119 #[doc(hidden)]
120 static _ON_PROCESS_START: fn(&$crate::ProcessStartArgs) = _on_process_start;
121 fn _on_process_start(args: &$crate::ProcessStartArgs) {
122 fn on_process_start(args: &$crate::ProcessStartArgs, handler: impl FnOnce(&$crate::ProcessStartArgs)) {
123 handler(args)
124 }
125 on_process_start(args, $closure)
126 }
127 };
128}
129
130#[cfg(target_arch = "wasm32")]
131#[doc(hidden)]
132#[macro_export]
133macro_rules! __on_process_start {
134 ($closure:expr) => {
135 $crate::wasm_process_start! {$crate,$closure}
136 };
137}
138
139#[doc(hidden)]
140#[cfg(target_arch = "wasm32")]
141pub use wasm_bindgen::prelude::wasm_bindgen;
142
143#[doc(hidden)]
144#[cfg(target_arch = "wasm32")]
145pub use zng_env_proc_macros::wasm_process_start;
146use zng_txt::Txt;
147
148#[cfg(target_arch = "wasm32")]
149std::thread_local! {
150 #[doc(hidden)]
151 pub static WASM_INIT: std::cell::RefCell<Vec<fn(&ProcessStartArgs)>> = const { std::cell::RefCell::new(vec![]) };
152}
153
154#[cfg(not(target_arch = "wasm32"))]
155#[doc(hidden)]
156#[linkme::distributed_slice]
157pub static ZNG_ENV_ON_PROCESS_START: [fn(&ProcessStartArgs)];
158
159#[cfg(not(target_arch = "wasm32"))]
160pub(crate) fn process_init() -> impl Drop {
161 process_init_impl(&ZNG_ENV_ON_PROCESS_START)
162}
163
164fn process_init_impl(handlers: &[fn(&ProcessStartArgs)]) -> MainExitHandler {
165 let process_state = std::mem::replace(
166 &mut *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock(),
167 ProcessLifetimeState::Inited,
168 );
169 assert_eq!(process_state, ProcessLifetimeState::BeforeInit, "init!() already called");
170
171 let mut yielded = vec![];
172 let mut next_handlers_count = handlers.len();
173 for h in handlers {
174 next_handlers_count -= 1;
175 let args = ProcessStartArgs {
176 next_handlers_count,
177 yield_count: 0,
178 yield_requested: AtomicBool::new(false),
179 };
180 h(&args);
181 if args.yield_requested.load(Ordering::Relaxed) {
182 yielded.push(h);
183 next_handlers_count += 1;
184 }
185 }
186
187 let mut yield_count = 0;
188 while !yielded.is_empty() {
189 yield_count += 1;
190 if yield_count > ProcessStartArgs::MAX_YIELD_COUNT {
191 eprintln!("start handlers requested `yield_start` more them 32 times");
192 break;
193 }
194
195 next_handlers_count = yielded.len();
196 for h in mem::take(&mut yielded) {
197 next_handlers_count -= 1;
198 let args = ProcessStartArgs {
199 next_handlers_count,
200 yield_count,
201 yield_requested: AtomicBool::new(false),
202 };
203 h(&args);
204 if args.yield_requested.load(Ordering::Relaxed) {
205 yielded.push(h);
206 next_handlers_count += 1;
207 }
208 }
209 }
210 MainExitHandler
211}
212
213#[cfg(target_arch = "wasm32")]
214pub(crate) fn process_init() -> impl Drop {
215 std::panic::set_hook(Box::new(console_error_panic_hook::hook));
216
217 let window = web_sys::window().expect("cannot 'init!', no window object");
218 let module = js_sys::Reflect::get(&window, &"__zng_env_init_module".into())
219 .expect("cannot 'init!', missing module in 'window.__zng_env_init_module'");
220
221 if module == wasm_bindgen::JsValue::undefined() || module == wasm_bindgen::JsValue::null() {
222 panic!("cannot 'init!', missing module in 'window.__zng_env_init_module'");
223 }
224
225 let module: js_sys::Object = module.into();
226
227 for entry in js_sys::Object::entries(&module) {
228 let entry: js_sys::Array = entry.into();
229 let ident = entry.get(0).as_string().expect("expected ident at entry[0]");
230
231 if ident.starts_with("__zng_env_start_") {
232 let func: js_sys::Function = entry.get(1).into();
233 if let Err(e) = func.call0(&wasm_bindgen::JsValue::NULL) {
234 panic!("'init!' function error, {e:?}");
235 }
236 }
237 }
238
239 process_init_impl(&WASM_INIT.with_borrow_mut(std::mem::take))
240}
241
242pub struct ProcessStartArgs {
246 pub next_handlers_count: usize,
248
249 pub yield_count: u16,
253
254 yield_requested: AtomicBool,
255}
256impl ProcessStartArgs {
257 pub const MAX_YIELD_COUNT: u16 = 32;
259
260 pub fn yield_once(&self) {
264 self.yield_requested.store(true, Ordering::Relaxed);
265 }
266}
267
268struct MainExitHandler;
269impl Drop for MainExitHandler {
270 fn drop(&mut self) {
271 run_exit_handlers(if std::thread::panicking() { 101 } else { 0 })
272 }
273}
274
275type ExitHandler = Box<dyn FnOnce(&ProcessExitArgs) + Send + 'static>;
276
277zng_unique_id::hot_static! {
278 static ON_PROCESS_EXIT: Mutex<Vec<ExitHandler>> = Mutex::new(vec![]);
279}
280
281pub fn exit(code: i32) -> ! {
285 run_exit_handlers(code);
286 std::process::exit(code)
287}
288
289fn run_exit_handlers(code: i32) {
290 *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock() = ProcessLifetimeState::Exiting;
291
292 let on_exit = mem::take(&mut *zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock());
293 let args = ProcessExitArgs { code };
294 for h in on_exit {
295 h(&args);
296 }
297}
298
299#[non_exhaustive]
301pub struct ProcessExitArgs {
302 pub code: i32,
304}
305
306pub fn on_process_exit(handler: impl FnOnce(&ProcessExitArgs) + Send + 'static) {
313 zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock().push(Box::new(handler))
314}
315
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub enum ProcessLifetimeState {
321 BeforeInit,
323 Inited,
325 Exiting,
327}
328
329zng_unique_id::hot_static! {
330 static PROCESS_LIFETIME_STATE: Mutex<ProcessLifetimeState> = Mutex::new(ProcessLifetimeState::BeforeInit);
331}
332zng_unique_id::hot_static! {
333 static PROCESS_NAME: Mutex<Txt> = Mutex::new(Txt::from_static(""));
334}
335
336pub fn process_lifetime_state() -> ProcessLifetimeState {
338 *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock()
339}
340
341pub fn process_name() -> Txt {
356 zng_unique_id::hot_static_ref!(PROCESS_NAME).lock().clone()
357}
358
359pub fn set_process_name(name: impl Into<Txt>) {
367 set_process_name_impl(name.into(), true);
368}
369
370pub fn init_process_name(name: impl Into<Txt>) -> bool {
376 set_process_name_impl(name.into(), false)
377}
378
379fn set_process_name_impl(new_name: Txt, replace: bool) -> bool {
380 let mut name = zng_unique_id::hot_static_ref!(PROCESS_NAME).lock();
381 if replace || name.is_empty() {
382 *name = new_name;
383 drop(name);
384 tracing::info!("pid: {}, name: {}", std::process::id(), process_name());
385 true
386 } else {
387 false
388 }
389}
390
391pub fn assert_inited() {
393 match process_lifetime_state() {
394 ProcessLifetimeState::BeforeInit => panic!("env not inited, please call `zng::env::init!()` in main"),
395 ProcessLifetimeState::Inited => {}
396 ProcessLifetimeState::Exiting => {
397 panic!("env not inited correctly, please call `zng::env::init!()` at the beginning of the actual main function")
398 }
399 }
400}