1use core::fmt;
2use std::{
3 mem,
4 sync::atomic::{AtomicU8, Ordering},
5};
6
7use parking_lot::Mutex;
8
9#[doc(hidden)]
10#[cfg(not(target_arch = "wasm32"))]
11pub use linkme as __linkme;
12
13#[macro_export]
85macro_rules! on_process_start {
86 ($closure:expr) => {
87 $crate::__on_process_start! {$closure}
88 };
89}
90
91#[cfg(not(target_arch = "wasm32"))]
92#[doc(hidden)]
93#[macro_export]
94macro_rules! __on_process_start {
95 ($closure:expr) => {
96 const _: () = {
97 #[$crate::__linkme::distributed_slice($crate::ZNG_ENV_ON_PROCESS_START)]
98 #[linkme(crate = $crate::__linkme)]
99 #[doc(hidden)]
100 static _ON_PROCESS_START: fn(&$crate::ProcessStartArgs) = _on_process_start;
101 #[doc(hidden)]
102 fn _on_process_start(args: &$crate::ProcessStartArgs) {
103 fn on_process_start(args: &$crate::ProcessStartArgs, handler: impl FnOnce(&$crate::ProcessStartArgs)) {
104 handler(args)
105 }
106 on_process_start(args, $closure)
107 }
108 };
109 };
110}
111
112#[cfg(target_arch = "wasm32")]
113#[doc(hidden)]
114#[macro_export]
115macro_rules! __on_process_start {
116 ($closure:expr) => {
117 $crate::wasm_process_start! {$crate,$closure}
118 };
119}
120
121#[doc(hidden)]
122#[cfg(target_arch = "wasm32")]
123pub use wasm_bindgen::prelude::wasm_bindgen;
124
125#[doc(hidden)]
126#[cfg(target_arch = "wasm32")]
127pub use zng_env_proc_macros::wasm_process_start;
128use zng_txt::Txt;
129
130#[cfg(target_arch = "wasm32")]
131std::thread_local! {
132 #[doc(hidden)]
133 pub static WASM_INIT: std::cell::RefCell<Vec<fn(&ProcessStartArgs)>> = const { std::cell::RefCell::new(vec![]) };
134}
135
136#[cfg(not(target_arch = "wasm32"))]
137#[doc(hidden)]
138#[linkme::distributed_slice]
139pub static ZNG_ENV_ON_PROCESS_START: [fn(&ProcessStartArgs)];
140
141#[cfg(not(target_arch = "wasm32"))]
142pub(crate) fn process_init() -> impl Drop {
143 process_init_impl(&ZNG_ENV_ON_PROCESS_START)
144}
145
146fn process_init_impl(handlers: &[fn(&ProcessStartArgs)]) -> MainExitHandler {
147 let _ = process_path();
149
150 let process_state = std::mem::replace(
151 &mut *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock(),
152 ProcessLifetimeState::Inited,
153 );
154 assert_eq!(process_state, ProcessLifetimeState::BeforeInit, "init!() already called");
155
156 let mut yielded = vec![];
157 let mut next_handlers_count = handlers.len();
158 for h in handlers {
159 next_handlers_count -= 1;
160 let args = ProcessStartArgs {
161 next_handlers_count,
162 yield_count: 0,
163 yield_requested: AtomicU8::new(0),
164 };
165 h(&args);
166 if args.yield_requested.load(Ordering::Relaxed) == ProcessStartArgs::YIELD_ONCE {
167 yielded.push(h);
168 next_handlers_count += 1;
169 }
170 }
171
172 let mut yield_count = 0;
173 while !yielded.is_empty() {
174 yield_count += 1;
175 if yield_count > ProcessStartArgs::MAX_YIELD_COUNT {
176 eprintln!("start handlers requested `yield_start` more them 32 times");
177 break;
178 }
179
180 next_handlers_count = yielded.len();
181 for h in mem::take(&mut yielded) {
182 next_handlers_count -= 1;
183 let args = ProcessStartArgs {
184 next_handlers_count,
185 yield_count,
186 yield_requested: AtomicU8::new(0),
187 };
188 h(&args);
189 if let ProcessStartArgs::YIELD_ONCE = args.yield_requested.load(Ordering::Relaxed) {
190 yielded.push(h);
191 next_handlers_count += 1;
192 }
193 }
194 }
195
196 MainExitHandler
197}
198
199#[cfg(target_arch = "wasm32")]
200pub(crate) fn process_init() -> impl Drop {
201 std::panic::set_hook(Box::new(console_error_panic_hook::hook));
202
203 let window = web_sys::window().expect("cannot 'init!', no window object");
204 let module = js_sys::Reflect::get(&window, &"__zng_env_init_module".into())
205 .expect("cannot 'init!', missing module in 'window.__zng_env_init_module'");
206
207 if module == wasm_bindgen::JsValue::undefined() || module == wasm_bindgen::JsValue::null() {
208 panic!("cannot 'init!', missing module in 'window.__zng_env_init_module'");
209 }
210
211 let module: js_sys::Object = module.into();
212
213 for entry in js_sys::Object::entries(&module) {
214 let entry: js_sys::Array = entry.into();
215 let ident = entry.get(0).as_string().expect("expected ident at entry[0]");
216
217 if ident.starts_with("__zng_env_start_") {
218 let func: js_sys::Function = entry.get(1).into();
219 if let Err(e) = func.call0(&wasm_bindgen::JsValue::NULL) {
220 panic!("'init!' function error, {e:?}");
221 }
222 }
223 }
224
225 process_init_impl(&WASM_INIT.with_borrow_mut(std::mem::take))
226}
227
228pub struct ProcessStartArgs {
232 pub next_handlers_count: usize,
234
235 pub yield_count: u16,
239
240 yield_requested: AtomicU8,
241}
242impl ProcessStartArgs {
243 pub const MAX_YIELD_COUNT: u16 = 32;
245
246 const YIELD_ONCE: u8 = 1;
247
248 pub fn yield_once(&self) {
269 self.yield_requested.store(Self::YIELD_ONCE, Ordering::Relaxed);
270 }
271}
272
273struct MainExitHandler;
274impl Drop for MainExitHandler {
275 fn drop(&mut self) {
276 run_exit_handlers(if std::thread::panicking() { 101 } else { 0 })
277 }
278}
279
280type ExitHandler = Box<dyn FnOnce(&ProcessExitArgs) + Send + 'static>;
281
282zng_unique_id::hot_static! {
283 static ON_PROCESS_EXIT: Mutex<Vec<ExitHandler>> = Mutex::new(vec![]);
284}
285
286pub fn exit(code: i32) -> ! {
290 run_exit_handlers(code);
291 std::process::exit(code)
292}
293
294fn run_exit_handlers(code: i32) {
295 *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock() = ProcessLifetimeState::Exiting;
296
297 let on_exit = mem::take(&mut *zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock());
298 let args = ProcessExitArgs { code };
299 for h in on_exit {
300 h(&args);
301 }
302}
303
304#[non_exhaustive]
306pub struct ProcessExitArgs {
307 pub code: i32,
309}
310
311pub fn on_process_exit(handler: impl FnOnce(&ProcessExitArgs) + Send + 'static) {
318 zng_unique_id::hot_static_ref!(ON_PROCESS_EXIT).lock().push(Box::new(handler))
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub enum ProcessLifetimeState {
326 BeforeInit,
328 Inited,
330 Exiting,
332}
333
334zng_unique_id::hot_static! {
335 static PROCESS_LIFETIME_STATE: Mutex<ProcessLifetimeState> = Mutex::new(ProcessLifetimeState::BeforeInit);
336}
337zng_unique_id::hot_static! {
338 static PROCESS_NAME: Mutex<Txt> = Mutex::new(Txt::from_static(""));
339}
340
341pub fn process_lifetime_state() -> ProcessLifetimeState {
343 *zng_unique_id::hot_static_ref!(PROCESS_LIFETIME_STATE).lock()
344}
345
346#[derive(PartialEq, Eq, Hash, Clone)]
353pub struct ProcessPath {
354 instance_id: u64,
355 process_ids: Box<[u32]>,
356}
357impl ProcessPath {
358 pub fn instance_id(&self) -> u64 {
360 self.instance_id
361 }
362
363 pub fn process_ids(&self) -> &[u32] {
365 &self.process_ids[..]
366 }
367
368 pub fn parent(&self) -> Option<Self> {
370 if self.process_ids.len() == 1 {
371 None
372 } else {
373 Some(Self {
374 instance_id: self.instance_id,
375 process_ids: self.process_ids[..self.process_ids.len() - 1].into(),
376 })
377 }
378 }
379}
380impl fmt::Debug for ProcessPath {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 if f.alternate() {
383 f.debug_struct("ProcessPath")
384 .field("instance_id", &self.instance_id)
385 .field("process_ids", &self.process_ids)
386 .finish()
387 } else {
388 write!(f, "ProcessPath({self})")
389 }
390 }
391}
392impl fmt::Display for ProcessPath {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 if f.alternate() {
396 write!(f, "{:x}", self.instance_id)?;
397 for id in &self.process_ids {
398 write!(f, "-{id}")?;
399 }
400 } else {
401 write!(f, "{:x}/", self.instance_id)?;
402 for id in &self.process_ids {
403 write!(f, "/{id}")?;
404 }
405 }
406 Ok(())
407 }
408}
409impl serde::Serialize for ProcessPath {
410 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
411 where
412 S: serde::Serializer,
413 {
414 if serializer.is_human_readable() {
415 serializer.collect_str(self)
416 } else {
417 (self.instance_id, &self.process_ids[..]).serialize(serializer)
418 }
419 }
420}
421impl<'de> serde::Deserialize<'de> for ProcessPath {
422 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
423 where
424 D: serde::Deserializer<'de>,
425 {
426 if deserializer.is_human_readable() {
427 struct FromStrVisitor;
428 impl<'de> serde::de::Visitor<'de> for FromStrVisitor {
429 type Value = ProcessPath;
430
431 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
432 write!(f, "process path string")
433 }
434
435 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
436 where
437 E: serde::de::Error,
438 {
439 v.parse().map_err(serde::de::Error::custom)
440 }
441 }
442 deserializer.deserialize_str(FromStrVisitor)
443 } else {
444 let (instance_id, process_ids) = <(u64, Box<[u32]>)>::deserialize(deserializer)?;
445 Ok(Self { instance_id, process_ids })
446 }
447 }
448}
449impl std::str::FromStr for ProcessPath {
450 type Err = ParseProcessPathError;
451
452 fn from_str(s: &str) -> Result<Self, Self::Err> {
453 parse_process_path(s, false)
454 }
455}
456fn parse_process_path(s: &str, extend: bool) -> Result<ProcessPath, ParseProcessPathError> {
457 if let Some((instance_id, p_ids)) = s.split_once("//")
458 && !instance_id.is_empty()
459 && !p_ids.is_empty()
460 {
461 let instance_id = u64::from_str_radix(instance_id, 16)?;
462 let mut process_ids = Vec::with_capacity(p_ids.split('/').count() + if extend { 1 } else { 0 });
463 for id in p_ids.split('/') {
464 let id = id.parse::<u32>()?;
465 process_ids.push(id);
466 }
467 if extend {
468 process_ids.push(std::process::id());
469 }
470 Ok(ProcessPath {
471 instance_id,
472 process_ids: process_ids.into_boxed_slice(),
473 })
474 } else {
475 Err(ParseProcessPathError::MissingPart)
476 }
477}
478
479#[derive(Debug, Clone, PartialEq, Eq)]
481pub enum ParseProcessPathError {
482 Int(std::num::ParseIntError),
484 MissingPart,
486}
487impl fmt::Display for ParseProcessPathError {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 match self {
490 ParseProcessPathError::Int(e) => fmt::Display::fmt(e, f),
491 ParseProcessPathError::MissingPart => write!(f, "missing part"),
492 }
493 }
494}
495impl std::error::Error for ParseProcessPathError {
496 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
497 match self {
498 ParseProcessPathError::Int(e) => Some(e),
499 ParseProcessPathError::MissingPart => None,
500 }
501 }
502}
503impl From<std::num::ParseIntError> for ParseProcessPathError {
504 fn from(e: std::num::ParseIntError) -> Self {
505 ParseProcessPathError::Int(e)
506 }
507}
508
509zng_unique_id::lazy_static! {
510 static ref PROCESS_PATH: ProcessPath = {
511 let path = match std::env::var("ZNG_PROCESS_PATH") {
512 Ok(s) => match parse_process_path(&s, true) {
513 Ok(p) => Some(p),
514 Err(e) => {
515 eprintln!("invalid ZNG_PROCESS_PATH, {s:?}, {e}");
516 None
517 }
518 },
519 Err(e) => match e {
520 std::env::VarError::NotPresent => None,
521 std::env::VarError::NotUnicode(s) => {
522 eprintln!("invalid ZNG_PROCESS_PATH, {s:?}");
523 None
524 }
525 },
526 };
527 let path = match path {
528 Some(p) => p,
529 None => ProcessPath {
530 #[cfg(target_arch = "wasm32")]
531 instance_id: 0,
532 #[cfg(not(target_arch = "wasm32"))]
533 instance_id: rand::random(),
534 process_ids: Box::new([std::process::id()]),
535 },
536 };
537 unsafe {
540 std::env::set_var("ZNG_PROCESS_PATH", path.to_string());
541 }
542 path
543 };
544}
545
546pub fn process_path() -> &'static ProcessPath {
548 &PROCESS_PATH
549}
550
551pub fn process_name() -> Txt {
566 zng_unique_id::hot_static_ref!(PROCESS_NAME).lock().clone()
567}
568
569pub fn set_process_name(name: impl Into<Txt>) {
577 set_process_name_impl(name.into(), true);
578}
579
580pub fn init_process_name(name: impl Into<Txt>) -> bool {
586 set_process_name_impl(name.into(), false)
587}
588
589fn set_process_name_impl(new_name: Txt, replace: bool) -> bool {
590 let mut name = zng_unique_id::hot_static_ref!(PROCESS_NAME).lock();
591 if replace || name.is_empty() {
592 *name = new_name;
593 drop(name);
594 tracing::info!("pid: {}, name: {}", std::process::id(), process_name());
596 true
597 } else {
598 false
599 }
600}
601
602pub fn assert_inited() {
604 match process_lifetime_state() {
605 ProcessLifetimeState::BeforeInit => panic!("env not inited, please call `zng::env::init!()` in main"),
606 ProcessLifetimeState::Inited => {}
607 ProcessLifetimeState::Exiting => {
608 panic!("env not inited correctly, please call `zng::env::init!()` at the beginning of the actual main function")
609 }
610 }
611}