1use ::std::cell::{Cell, RefCell};
12use ::std::time::Instant;
13
14use mozjs::jsapi::*;
15use mozjs::jsval::{
16 BooleanValue, Int32Value, JSVal, NullValue, ObjectValue, StringValue, UndefinedValue,
17};
18use mozjs::rooted;
19use mozjs::rust::wrappers2 as w2;
20
21use crate::require::cache_builtin;
22
23struct ClusterPumpEntry {
52 key: String,
54 pins: bool,
57 last_alive: bool,
60}
61
62thread_local! {
63 static CLUSTER_PUMPS: RefCell<Vec<ClusterPumpEntry>> = const { RefCell::new(Vec::new()) };
64 static CLUSTER_PUMP_KEY: Cell<u64> = const { Cell::new(1) };
65 static CLUSTER_PUMP_LAST_TICK: Cell<Option<Instant>> = const { Cell::new(None) };
68}
69
70#[allow(unsafe_op_in_unsafe_fn)]
74unsafe extern "C" fn cluster_pump_register(
75 cx: *mut JSContext,
76 argc: u32,
77 vp: *mut JSVal,
78) -> bool {
79 let args = CallArgs::from_vp(vp, argc);
80 if argc == 0 || !(*args.get(0).ptr).is_object() {
81 args.rval().set(BooleanValue(false));
82 return true;
83 }
84 let pins = if argc > 1 {
85 let v = *args.get(1).ptr;
86 v.is_boolean() && v.to_boolean()
87 } else {
88 false
89 };
90 let fn_obj = (*args.get(0).ptr).to_object();
91 let key = format!("p{}", CLUSTER_PUMP_KEY.with(|c| {
92 let v = c.get();
93 c.set(v + 1);
94 v
95 }));
96 crate::gc_store::gc_store_insert_ns(cx, "cluster-pump", &key, fn_obj);
97 CLUSTER_PUMPS.with(|p| {
98 p.borrow_mut().push(ClusterPumpEntry {
99 key,
100 pins,
101 last_alive: pins,
102 });
103 });
104 args.rval().set(BooleanValue(true));
105 true
106}
107
108pub fn cluster_pump_all(raw_cx: *mut JSContext) {
114 let due = CLUSTER_PUMP_LAST_TICK.with(|t| match t.get() {
115 Some(last) => last.elapsed().as_millis() >= 10,
116 None => true,
117 });
118 if !due {
119 return;
120 }
121 CLUSTER_PUMP_LAST_TICK.with(|t| t.set(Some(Instant::now())));
122
123 let snapshot: Vec<(String, bool)> = CLUSTER_PUMPS
126 .with(|p| p.borrow().iter().map(|e| (e.key.clone(), e.pins)).collect());
127 if snapshot.is_empty() {
128 return;
129 }
130
131 for (key, _pins) in snapshot {
132 let Some(pump_fn) = crate::gc_store::gc_store_get_ns(raw_cx, "cluster-pump", &key) else {
133 CLUSTER_PUMPS.with(|p| p.borrow_mut().retain(|e| e.key != key));
135 continue;
136 };
137 let alive = unsafe { call_cluster_pump(raw_cx, pump_fn) };
138 CLUSTER_PUMPS.with(|p| {
139 let mut pumps = p.borrow_mut();
140 if let Some(entry) = pumps.iter_mut().find(|e| e.key == key) {
141 entry.last_alive = alive;
147 }
148 });
149 }
150}
151
152unsafe fn call_cluster_pump(raw_cx: *mut JSContext, pump_fn: *mut JSObject) -> bool {
159 let cx_ref = &mut mozjs::context::JSContext::from_ptr(
160 ::std::ptr::NonNull::new_unchecked(raw_cx),
161 );
162 let global = CurrentGlobalOrNull(raw_cx);
163 if global.is_null() {
164 return false;
165 }
166 rooted!(&in(cx_ref) let global_r = global);
167 rooted!(&in(cx_ref) let fval = ObjectValue(pump_fn));
168 let args = HandleValueArray {
169 length_: 0,
170 elements_: ::std::ptr::null(),
171 };
172 let mut rval = UndefinedValue();
173 let ok = JS_CallFunctionValue(
174 raw_cx,
175 global_r.handle().into(),
176 fval.handle().into(),
177 &args,
178 MutableHandle::<Value> {
179 _phantom_0: ::std::marker::PhantomData,
180 ptr: &mut rval,
181 },
182 );
183 if !ok {
184 JS_ClearPendingException(raw_cx);
185 return false;
186 }
187 rval.is_boolean() && rval.to_boolean()
188}
189
190pub fn cluster_loop_alive() -> bool {
194 CLUSTER_PUMPS.with(|p| p.borrow().iter().any(|e| e.pins && e.last_alive))
195}
196
197fn is_worker_env(worker_id: Option<&str>) -> bool {
207 match worker_id {
208 Some(s) => s.parse::<u32>().map(|n| n >= 1).unwrap_or(false),
209 None => false,
210 }
211}
212
213static EXEC_TIME_WORKER_ID: ::std::sync::OnceLock<Option<String>> = ::std::sync::OnceLock::new();
234
235#[cfg(target_os = "linux")]
237extern "C" fn snapshot_exec_worker_id() {
238 let _ = EXEC_TIME_WORKER_ID.set(::std::env::var("BAO_CLUSTER_WORKER_ID").ok());
239}
240
241#[cfg(target_os = "linux")]
245#[used]
246#[unsafe(link_section = ".init_array")]
247static CAPTURE_EXEC_WORKER_ID: extern "C" fn() = snapshot_exec_worker_id;
248
249fn is_cluster_worker() -> bool {
251 let raw = EXEC_TIME_WORKER_ID
256 .get()
257 .cloned()
258 .unwrap_or_else(|| ::std::env::var("BAO_CLUSTER_WORKER_ID").ok());
259 is_worker_env(raw.as_deref())
260}
261
262pub fn install(cx: &mut mozjs::context::JSContext) {
265 rooted!(&in(cx) let obj = unsafe { w2::JS_NewPlainObject(cx) });
266 if obj.get().is_null() {
267 return;
268 }
269
270 let is_worker = is_cluster_worker();
271 let is_primary = !is_worker;
272
273 unsafe {
274 let raw_cx = cx.raw_cx();
275
276 rooted!(&in(cx) let is_primary_val = BooleanValue(is_primary));
278 let _ = JS_DefineProperty(
279 raw_cx,
280 obj.handle().into(),
281 c"isPrimary".as_ptr(),
282 is_primary_val.handle().into(),
283 JSPROP_ENUMERATE as u32,
284 );
285
286 rooted!(&in(cx) let is_master_val = BooleanValue(is_primary));
288 let _ = JS_DefineProperty(
289 raw_cx,
290 obj.handle().into(),
291 c"isMaster".as_ptr(),
292 is_master_val.handle().into(),
293 JSPROP_ENUMERATE as u32,
294 );
295
296 rooted!(&in(cx) let is_worker_val = BooleanValue(is_worker));
298 let _ = JS_DefineProperty(
299 raw_cx,
300 obj.handle().into(),
301 c"isWorker".as_ptr(),
302 is_worker_val.handle().into(),
303 JSPROP_ENUMERATE as u32,
304 );
305
306 rooted!(&in(cx) let workers_obj = w2::JS_NewPlainObject(cx));
308 if !workers_obj.get().is_null() {
309 rooted!(&in(cx) let workers_val = ObjectValue(workers_obj.get()));
310 let _ = JS_DefineProperty(
311 raw_cx,
312 obj.handle().into(),
313 c"workers".as_ptr(),
314 workers_val.handle().into(),
315 JSPROP_ENUMERATE as u32,
316 );
317 }
318
319 rooted!(&in(cx) let settings_obj = w2::JS_NewPlainObject(cx));
321 if !settings_obj.get().is_null() {
322 rooted!(&in(cx) let settings_val = ObjectValue(settings_obj.get()));
323 let _ = JS_DefineProperty(
324 raw_cx,
325 obj.handle().into(),
326 c"settings".as_ptr(),
327 settings_val.handle().into(),
328 JSPROP_ENUMERATE as u32,
329 );
330 }
331
332 if is_worker {
334 rooted!(&in(cx) let worker_obj = make_worker_object(cx, raw_cx));
335 if !worker_obj.get().is_null() {
336 rooted!(&in(cx) let worker_val = ObjectValue(worker_obj.get()));
337 let _ = JS_DefineProperty(
338 raw_cx,
339 obj.handle().into(),
340 c"worker".as_ptr(),
341 worker_val.handle().into(),
342 JSPROP_ENUMERATE as u32,
343 );
344 }
345 } else {
346 rooted!(&in(cx) let worker_val = UndefinedValue());
347 let _ = JS_DefineProperty(
348 raw_cx,
349 obj.handle().into(),
350 c"worker".as_ptr(),
351 worker_val.handle().into(),
352 JSPROP_ENUMERATE as u32,
353 );
354 }
355
356 let fork_fn = JS_NewFunction(raw_cx, Some(cluster_fork), 0, 0, c"fork".as_ptr());
358 if !fork_fn.is_null() {
359 let fn_obj = JS_GetFunctionObject(fork_fn);
360 if !fn_obj.is_null() {
361 rooted!(&in(cx) let val = ObjectValue(fn_obj));
362 let _ = JS_DefineProperty(
363 raw_cx,
364 obj.handle().into(),
365 c"fork".as_ptr(),
366 val.handle().into(),
367 JSPROP_ENUMERATE as u32,
368 );
369 }
370 }
371
372 let disconnect_fn = JS_NewFunction(
374 raw_cx,
375 Some(cluster_disconnect),
376 0,
377 0,
378 c"disconnect".as_ptr(),
379 );
380 if !disconnect_fn.is_null() {
381 let fn_obj = JS_GetFunctionObject(disconnect_fn);
382 if !fn_obj.is_null() {
383 rooted!(&in(cx) let val = ObjectValue(fn_obj));
384 let _ = JS_DefineProperty(
385 raw_cx,
386 obj.handle().into(),
387 c"disconnect".as_ptr(),
388 val.handle().into(),
389 JSPROP_ENUMERATE as u32,
390 );
391 }
392 }
393
394 let setup_fn = JS_NewFunction(
396 raw_cx,
397 Some(cluster_setup_primary),
398 1,
399 0,
400 c"setupPrimary".as_ptr(),
401 );
402 if !setup_fn.is_null() {
403 let fn_obj = JS_GetFunctionObject(setup_fn);
404 if !fn_obj.is_null() {
405 rooted!(&in(cx) let val = ObjectValue(fn_obj));
406 let _ = JS_DefineProperty(
407 raw_cx,
408 obj.handle().into(),
409 c"setupPrimary".as_ptr(),
410 val.handle().into(),
411 JSPROP_ENUMERATE as u32,
412 );
413 }
414 }
415 let setup_master_fn = JS_NewFunction(
416 raw_cx,
417 Some(cluster_setup_primary),
418 1,
419 0,
420 c"setupMaster".as_ptr(),
421 );
422 if !setup_master_fn.is_null() {
423 let fn_obj = JS_GetFunctionObject(setup_master_fn);
424 if !fn_obj.is_null() {
425 rooted!(&in(cx) let val = ObjectValue(fn_obj));
426 let _ = JS_DefineProperty(
427 raw_cx,
428 obj.handle().into(),
429 c"setupMaster".as_ptr(),
430 val.handle().into(),
431 JSPROP_ENUMERATE as u32,
432 );
433 }
434 }
435
436 rooted!(&in(cx) let sched = Int32Value(2));
438 let _ = JS_DefineProperty(
439 raw_cx,
440 obj.handle().into(),
441 c"schedulingPolicy".as_ptr(),
442 sched.handle().into(),
443 JSPROP_ENUMERATE as u32,
444 );
445
446 rooted!(&in(cx) let sched_none = Int32Value(1));
448 let _ = JS_DefineProperty(
449 raw_cx,
450 obj.handle().into(),
451 c"SCHED_NONE".as_ptr(),
452 sched_none.handle().into(),
453 JSPROP_ENUMERATE as u32,
454 );
455 rooted!(&in(cx) let sched_rr = Int32Value(2));
456 let _ = JS_DefineProperty(
457 raw_cx,
458 obj.handle().into(),
459 c"SCHED_RR".as_ptr(),
460 sched_rr.handle().into(),
461 JSPROP_ENUMERATE as u32,
462 );
463
464 let boot_fn = JS_NewFunction(
466 raw_cx,
467 Some(cluster_worker_boot),
468 1,
469 0,
470 c"__cluster_worker_boot".as_ptr(),
471 );
472 if !boot_fn.is_null() {
473 let fn_obj = JS_GetFunctionObject(boot_fn);
474 if !fn_obj.is_null() {
475 rooted!(&in(cx) let val = ObjectValue(fn_obj));
476 let _ = JS_DefineProperty(
477 raw_cx,
478 obj.handle().into(),
479 c"__cluster_worker_boot".as_ptr(),
480 val.handle().into(),
481 0,
482 );
483 }
484 }
485 let kill_fn = JS_NewFunction(
486 raw_cx,
487 Some(cluster_worker_kill),
488 2,
489 0,
490 c"__cluster_worker_kill".as_ptr(),
491 );
492 if !kill_fn.is_null() {
493 let fn_obj = JS_GetFunctionObject(kill_fn);
494 if !fn_obj.is_null() {
495 rooted!(&in(cx) let val = ObjectValue(fn_obj));
496 let _ = JS_DefineProperty(
497 raw_cx,
498 obj.handle().into(),
499 c"__cluster_worker_kill".as_ptr(),
500 val.handle().into(),
501 0,
502 );
503 }
504 }
505 let ipc_send_fn = JS_NewFunction(
506 raw_cx,
507 Some(cluster_ipc_send),
508 2,
509 0,
510 c"__cluster_ipc_send".as_ptr(),
511 );
512 if !ipc_send_fn.is_null() {
513 let fn_obj = JS_GetFunctionObject(ipc_send_fn);
514 if !fn_obj.is_null() {
515 rooted!(&in(cx) let val = ObjectValue(fn_obj));
516 let _ = JS_DefineProperty(
517 raw_cx,
518 obj.handle().into(),
519 c"__cluster_ipc_send".as_ptr(),
520 val.handle().into(),
521 0,
522 );
523 }
524 }
525 let pump_register_fn = JS_NewFunction(
530 raw_cx,
531 Some(cluster_pump_register),
532 2,
533 0,
534 c"__cluster_pump_register".as_ptr(),
535 );
536 if !pump_register_fn.is_null() {
537 let fn_obj = JS_GetFunctionObject(pump_register_fn);
538 if !fn_obj.is_null() {
539 rooted!(&in(cx) let val = ObjectValue(fn_obj));
540 let _ = JS_DefineProperty(
541 raw_cx,
542 obj.handle().into(),
543 c"__cluster_pump_register".as_ptr(),
544 val.handle().into(),
545 0,
546 );
547 }
548 }
549 }
550
551 cache_builtin(cx, "cluster", obj.get());
552
553 unsafe {
555 let c_filename = bun_core::ZBox::from_bytes("node:cluster".as_bytes());
556 let opts = mozjs::glue::NewCompileOptions(cx.raw_cx(), c_filename.as_ptr(), 1);
557 if !opts.is_null() {
558 let mut src = mozjs::rust::transform_str_to_source_text(CLUSTER_JS);
559 let mut rval = UndefinedValue();
560 let rval_handle = MutableHandle::<Value> {
561 _phantom_0: ::std::marker::PhantomData,
562 ptr: &mut rval,
563 };
564 let _ = mozjs_sys::jsapi::JS::Evaluate2(cx.raw_cx(), opts, &mut src, rval_handle);
565 libc::free(opts as *mut _);
566 }
567 }
568}
569
570unsafe fn make_worker_object(
572 cx: &mut mozjs::context::JSContext,
573 _raw_cx: *mut JSContext,
574) -> *mut JSObject {
575 unsafe {
576 let worker_obj = w2::JS_NewPlainObject(cx);
577 if worker_obj.is_null() {
578 return ::std::ptr::null_mut();
579 }
580 rooted!(&in(cx) let worker_r = worker_obj);
581 let worker_h = worker_r.handle().into();
582
583 let worker_id: i32 = ::std::env::var("BAO_CLUSTER_WORKER_ID")
585 .ok()
586 .and_then(|s| s.parse().ok())
587 .unwrap_or(0);
588 rooted!(&in(cx) let id_val = Int32Value(worker_id));
589 JS_DefineProperty(
590 cx.raw_cx(),
591 worker_h,
592 c"id".as_ptr(),
593 id_val.handle().into(),
594 JSPROP_ENUMERATE as u32,
595 );
596
597 rooted!(&in(cx) let null_v = NullValue());
599 JS_DefineProperty(
600 cx.raw_cx(),
601 worker_h,
602 c"process".as_ptr(),
603 null_v.handle().into(),
604 JSPROP_ENUMERATE as u32,
605 );
606
607 rooted!(&in(cx) let connected_v = BooleanValue(true));
609 JS_DefineProperty(
610 cx.raw_cx(),
611 worker_h,
612 c"isConnected".as_ptr(),
613 connected_v.handle().into(),
614 JSPROP_ENUMERATE as u32,
615 );
616
617 rooted!(&in(cx) let dead_v = BooleanValue(false));
619 JS_DefineProperty(
620 cx.raw_cx(),
621 worker_h,
622 c"isDead".as_ptr(),
623 dead_v.handle().into(),
624 JSPROP_ENUMERATE as u32,
625 );
626
627 rooted!(&in(cx) let ead_v = BooleanValue(false));
629 JS_DefineProperty(
630 cx.raw_cx(),
631 worker_h,
632 c"exitedAfterDisconnect".as_ptr(),
633 ead_v.handle().into(),
634 JSPROP_ENUMERATE as u32,
635 );
636
637 rooted!(&in(cx) let events_obj = w2::JS_NewPlainObject(cx));
639 if !events_obj.get().is_null() {
640 rooted!(&in(cx) let events_val = ObjectValue(events_obj.get()));
641 JS_DefineProperty(
642 cx.raw_cx(),
643 worker_h,
644 c"_events".as_ptr(),
645 events_val.handle().into(),
646 0,
647 );
648 }
649
650 worker_r.get()
651 }
652}
653
654#[allow(unsafe_op_in_unsafe_fn)]
676unsafe extern "C" fn cluster_fork(
677 cx: *mut JSContext,
678 argc: u32,
679 vp: *mut mozjs::jsval::JSVal,
680) -> bool {
681 let args = CallArgs::from_vp(vp, argc);
682
683 if let ::std::result::Result::Err(e) = crate::permission_bridge::check_run() {
684 let c_msg = bun_core::ZBox::from_bytes(e.as_bytes());
685 JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
686 return false;
687 }
688
689 let mut wrapped_cx =
691 mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
692 let cx_ref = &mut wrapped_cx;
693
694 let script_path = {
695 rooted!(&in(cx_ref) let global = CurrentGlobalOrNull(cx));
696 let mut process_val = UndefinedValue();
697 JS_GetProperty(
698 cx,
699 global.handle().into(),
700 c"process".as_ptr(),
701 MutableHandle::<Value> {
702 _phantom_0: ::std::marker::PhantomData,
703 ptr: &mut process_val,
704 },
705 );
706 if process_val.is_object() {
707 let process_obj = process_val.to_object();
708 rooted!(&in(cx_ref) let process_r = process_obj);
709 let mut argv_val = UndefinedValue();
710 JS_GetProperty(
711 cx,
712 process_r.handle().into(),
713 c"argv".as_ptr(),
714 MutableHandle::<Value> {
715 _phantom_0: ::std::marker::PhantomData,
716 ptr: &mut argv_val,
717 },
718 );
719 if argv_val.is_object() {
720 let argv_obj = argv_val.to_object();
721 rooted!(&in(cx_ref) let argv_r = argv_obj);
722 let mut first = UndefinedValue();
726 JS_GetElement(
727 cx,
728 argv_r.handle().into(),
729 1,
730 MutableHandle::<Value> {
731 _phantom_0: ::std::marker::PhantomData,
732 ptr: &mut first,
733 },
734 );
735 let mut second = UndefinedValue();
736 JS_GetElement(
737 cx,
738 argv_r.handle().into(),
739 2,
740 MutableHandle::<Value> {
741 _phantom_0: ::std::marker::PhantomData,
742 ptr: &mut second,
743 },
744 );
745 if first.is_string()
746 && crate::js_to_rust_string(cx, first) == "run"
747 && second.is_string()
748 {
749 crate::js_to_rust_string(cx, second)
750 } else if first.is_string() {
751 crate::js_to_rust_string(cx, first)
752 } else {
753 String::new()
754 }
755 } else {
756 String::new()
757 }
758 } else {
759 String::new()
760 }
761 };
762
763 if script_path.is_empty() {
764 JS_ReportErrorUTF8(
765 cx,
766 c"cluster.fork(): cannot determine script path (process.argv[1] is empty)".as_ptr(),
767 );
768 args.rval().set(UndefinedValue());
769 return false;
770 }
771
772 let worker_id: i32 = {
774 if let Some(cluster_mod) = crate::require::get_builtin(cx_ref.raw_cx(), "cluster") {
775 if !cluster_mod.is_null() {
776 rooted!(&in(cx_ref) let cm_r = cluster_mod);
777 let mut settings_val = UndefinedValue();
778 JS_GetProperty(
779 cx,
780 cm_r.handle().into(),
781 c"settings".as_ptr(),
782 MutableHandle::<Value> {
783 _phantom_0: ::std::marker::PhantomData,
784 ptr: &mut settings_val,
785 },
786 );
787 if settings_val.is_object() {
788 let settings_obj = settings_val.to_object();
789 rooted!(&in(cx_ref) let settings_r = settings_obj);
790 let mut next_id_val = UndefinedValue();
791 JS_GetProperty(
792 cx,
793 settings_r.handle().into(),
794 c"_nextId".as_ptr(),
795 MutableHandle::<Value> {
796 _phantom_0: ::std::marker::PhantomData,
797 ptr: &mut next_id_val,
798 },
799 );
800 if next_id_val.is_int32() {
801 let id = next_id_val.to_int32();
802 let new_id = id + 1;
803 rooted!(&in(cx_ref) let new_id_v = Int32Value(new_id));
804 JS_SetProperty(
805 cx,
806 settings_r.handle().into(),
807 c"_nextId".as_ptr(),
808 new_id_v.handle().into(),
809 );
810 id
811 } else {
812 rooted!(&in(cx_ref) let init_v = Int32Value(2));
813 JS_SetProperty(
814 cx,
815 settings_r.handle().into(),
816 c"_nextId".as_ptr(),
817 init_v.handle().into(),
818 );
819 1
820 }
821 } else {
822 1
823 }
824 } else {
825 1
826 }
827 } else {
828 1
829 }
830 };
831
832 let exec_str = ::std::env::var("BAO_CLUSTER_EXEC").unwrap_or_else(|_| {
836 ::std::env::current_exe()
837 .unwrap_or_else(|_| ::std::path::PathBuf::from("bao"))
838 .to_string_lossy()
839 .into_owned()
840 });
841
842 let primary_pid = ::std::process::id();
845 let mut env_map: ::std::collections::BTreeMap<String, String> =
846 ::std::env::vars().collect();
847 if argc > 0 && (*args.get(0).ptr).is_object() {
848 let env_obj = (*args.get(0).ptr).to_object();
849 rooted!(&in(cx_ref) let env_r = env_obj);
850 let mut ids = mozjs::rust::IdVector::new(cx);
851 if GetPropertyKeys(cx, env_r.handle().into(), JSITER_OWNONLY, ids.handle_mut()) {
852 for jsid in &*ids {
853 if !jsid.is_string() {
854 continue;
855 }
856 let key_ptr = jsid.to_string();
857 let key = mozjs::conversions::unsafe_jsstr_to_string(
858 cx,
859 ::std::ptr::NonNull::new_unchecked(key_ptr),
860 );
861 let c_key = bun_core::ZBox::from_bytes(key.as_bytes());
862 let mut v_val = UndefinedValue();
863 JS_GetProperty(
864 cx,
865 env_r.handle().into(),
866 c_key.as_ptr(),
867 MutableHandle::<Value> {
868 _phantom_0: ::std::marker::PhantomData,
869 ptr: &mut v_val,
870 },
871 );
872 let val = if v_val.is_string() {
873 crate::js_to_rust_string(cx, v_val)
874 } else if v_val.is_int32() {
875 v_val.to_int32().to_string()
876 } else if v_val.is_boolean() {
877 if v_val.to_boolean() {
878 "true".to_string()
879 } else {
880 "false".to_string()
881 }
882 } else {
883 continue;
884 };
885 env_map.insert(key, val);
886 }
887 }
888 }
889 env_map.insert("BAO_CLUSTER_WORKER_ID".to_string(), worker_id.to_string());
890 env_map.insert(
891 "BAO_CLUSTER_PRIMARY_PID".to_string(),
892 primary_pid.to_string(),
893 );
894 env_map.insert("BAO_CLUSTER_IPC_FD".to_string(), "3".to_string());
895 let env_entries: Vec<Box<[u8]>> = env_map
896 .into_iter()
897 .map(|(k, v)| format!("{}={}", k, v).into_bytes().into_boxed_slice())
898 .collect();
899
900 let argv: Vec<Box<[u8]>> = vec![
902 exec_str.as_bytes().to_vec().into_boxed_slice(),
903 b"run".to_vec().into_boxed_slice(),
904 script_path.as_bytes().to_vec().into_boxed_slice(),
905 ];
906
907 let pid = match super::node_child_process::spawn_cluster_worker(argv, env_entries) {
909 Ok(p) => p,
910 Err(msg) => {
911 let c_msg = bun_core::ZBox::from_bytes(msg.as_bytes());
912 JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
913 return false;
914 }
915 };
916
917 let worker_obj = mozjs_sys::jsapi::JS_NewPlainObject(cx);
919 if worker_obj.is_null() {
920 args.rval().set(UndefinedValue());
921 return true;
922 }
923 rooted!(&in(cx_ref) let worker_r = worker_obj);
924 let worker_h = worker_r.handle().into();
925
926 rooted!(&in(cx_ref) let id_v = Int32Value(worker_id));
928 JS_DefineProperty(
929 cx,
930 worker_h,
931 c"id".as_ptr(),
932 id_v.handle().into(),
933 JSPROP_ENUMERATE as u32,
934 );
935
936 let proc_obj = w2::JS_NewPlainObject(cx_ref);
939 if !proc_obj.is_null() {
940 rooted!(&in(cx_ref) let proc_r = proc_obj);
941 let proc_h = proc_r.handle().into();
942
943 rooted!(&in(cx_ref) let pid_v = Int32Value(pid));
944 JS_DefineProperty(
945 cx,
946 proc_h,
947 c"pid".as_ptr(),
948 pid_v.handle().into(),
949 JSPROP_ENUMERATE as u32,
950 );
951
952 rooted!(&in(cx_ref) let ec_v = NullValue());
953 JS_DefineProperty(
954 cx,
955 proc_h,
956 c"exitCode".as_ptr(),
957 ec_v.handle().into(),
958 JSPROP_ENUMERATE as u32,
959 );
960
961 let proc_val = ObjectValue(proc_r.get());
962 rooted!(&in(cx_ref) let pv = proc_val);
963 JS_DefineProperty(
964 cx,
965 worker_h,
966 c"process".as_ptr(),
967 pv.handle().into(),
968 JSPROP_ENUMERATE as u32,
969 );
970 }
971
972 rooted!(&in(cx_ref) let conn_v = BooleanValue(true));
974 JS_DefineProperty(
975 cx,
976 worker_h,
977 c"isConnected".as_ptr(),
978 conn_v.handle().into(),
979 JSPROP_ENUMERATE as u32,
980 );
981
982 rooted!(&in(cx_ref) let dead_v = BooleanValue(false));
985 JS_DefineProperty(
986 cx,
987 worker_h,
988 c"isDead".as_ptr(),
989 dead_v.handle().into(),
990 JSPROP_ENUMERATE as u32,
991 );
992
993 rooted!(&in(cx_ref) let ead_v = BooleanValue(false));
995 JS_DefineProperty(
996 cx,
997 worker_h,
998 c"exitedAfterDisconnect".as_ptr(),
999 ead_v.handle().into(),
1000 JSPROP_ENUMERATE as u32,
1001 );
1002
1003 rooted!(&in(cx_ref) let npid_v = Int32Value(pid));
1005 JS_DefineProperty(cx, worker_h, c"_pid".as_ptr(), npid_v.handle().into(), 0);
1006
1007 w2::JS_DefineFunction(
1013 cx_ref,
1014 worker_r.handle(),
1015 c"send".as_ptr(),
1016 Some(cluster_worker_send),
1017 2,
1018 JSPROP_ENUMERATE as u32,
1019 );
1020 w2::JS_DefineFunction(
1022 cx_ref,
1023 worker_r.handle(),
1024 c"disconnect".as_ptr(),
1025 Some(cluster_worker_disconnect),
1026 0,
1027 JSPROP_ENUMERATE as u32,
1028 );
1029 rooted!(&in(cx_ref) let ipcfd_v = Int32Value(3));
1031 JS_DefineProperty(cx, worker_h, c"_ipcFd".as_ptr(), ipcfd_v.handle().into(), 0);
1032
1033 {
1035 if let Some(cluster_mod) = crate::require::get_builtin(cx_ref.raw_cx(), "cluster") {
1036 if !cluster_mod.is_null() {
1037 rooted!(&in(cx_ref) let cm_r = cluster_mod);
1038 let mut workers_val = UndefinedValue();
1039 JS_GetProperty(
1040 cx,
1041 cm_r.handle().into(),
1042 c"workers".as_ptr(),
1043 MutableHandle::<Value> {
1044 _phantom_0: ::std::marker::PhantomData,
1045 ptr: &mut workers_val,
1046 },
1047 );
1048 if workers_val.is_object() {
1049 let workers_obj = workers_val.to_object();
1050 rooted!(&in(cx_ref) let workers_r = workers_obj);
1051 let worker_val = ObjectValue(worker_r.get());
1052 rooted!(&in(cx_ref) let wv = worker_val);
1053 let id_c_str = bun_core::ZBox::from_bytes(format!("{}", worker_id).as_bytes());
1054 JS_SetProperty(
1055 cx,
1056 workers_r.handle().into(),
1057 id_c_str.as_ptr(),
1058 wv.handle().into(),
1059 );
1060 }
1061 }
1062 }
1063 }
1064
1065 args.rval().set(ObjectValue(worker_r.get()));
1066 true
1067}
1068
1069#[allow(unsafe_op_in_unsafe_fn)]
1071unsafe extern "C" fn cluster_disconnect(
1072 cx: *mut JSContext,
1073 _argc: u32,
1074 vp: *mut mozjs::jsval::JSVal,
1075) -> bool {
1076 let args = CallArgs::from_vp(vp, _argc);
1077
1078 let mut wrapped_cx =
1080 mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
1081 let cx_ref = &mut wrapped_cx;
1082
1083 if let Some(cluster_mod) = crate::require::get_builtin(cx_ref.raw_cx(), "cluster") {
1084 if !cluster_mod.is_null() {
1085 rooted!(&in(cx_ref) let cm_r = cluster_mod);
1086 let mut workers_val = UndefinedValue();
1087 JS_GetProperty(
1088 cx,
1089 cm_r.handle().into(),
1090 c"workers".as_ptr(),
1091 MutableHandle::<Value> {
1092 _phantom_0: ::std::marker::PhantomData,
1093 ptr: &mut workers_val,
1094 },
1095 );
1096 if workers_val.is_object() {
1097 let workers_obj = workers_val.to_object();
1098 rooted!(&in(cx_ref) let workers_r = workers_obj);
1099 let disconnected_v = BooleanValue(true);
1104 rooted!(&in(cx_ref) let dv = disconnected_v);
1105 JS_SetProperty(
1106 cx,
1107 cm_r.handle().into(),
1108 c"_disconnecting".as_ptr(),
1109 dv.handle().into(),
1110 );
1111 }
1112 }
1113 }
1114
1115 args.rval().set(UndefinedValue());
1116 true
1117}
1118
1119#[allow(unsafe_op_in_unsafe_fn)]
1121unsafe extern "C" fn cluster_setup_primary(
1122 cx: *mut JSContext,
1123 argc: u32,
1124 vp: *mut mozjs::jsval::JSVal,
1125) -> bool {
1126 let args = CallArgs::from_vp(vp, argc);
1127
1128 let mut wrapped_cx =
1129 mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
1130 let cx_ref = &mut wrapped_cx;
1131
1132 if argc > 0 {
1134 let settings_val = *args.get(0).ptr;
1135 if settings_val.is_object() {
1136 if let Some(cluster_mod) = crate::require::get_builtin(cx_ref.raw_cx(), "cluster") {
1137 if !cluster_mod.is_null() {
1138 rooted!(&in(cx_ref) let cm_r = cluster_mod);
1139 rooted!(&in(cx_ref) let sv = settings_val);
1140 JS_SetProperty(
1141 cx,
1142 cm_r.handle().into(),
1143 c"settings".as_ptr(),
1144 sv.handle().into(),
1145 );
1146 }
1147 }
1148 }
1149 }
1150
1151 args.rval().set(UndefinedValue());
1152 true
1153}
1154
1155#[allow(unsafe_op_in_unsafe_fn)]
1167unsafe extern "C" fn cluster_worker_send(
1168 cx: *mut JSContext,
1169 argc: u32,
1170 vp: *mut JSVal,
1171) -> bool {
1172 let args = CallArgs::from_vp(vp, argc);
1173
1174 let this_v = *args.thisv().ptr;
1176 let this_obj = if this_v.is_object() {
1177 this_v.to_object()
1178 } else {
1179 ::std::ptr::null_mut::<JSObject>()
1180 };
1181
1182 let mut wrapped_cx =
1183 mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
1184 let cx_ref = &mut wrapped_cx;
1185
1186 rooted!(&in(cx_ref) let this_r = this_obj);
1187 let mut pid_v = UndefinedValue();
1188 JS_GetProperty(
1189 cx,
1190 this_r.handle().into(),
1191 c"_pid".as_ptr(),
1192 MutableHandle::<Value> {
1193 _phantom_0: ::std::marker::PhantomData,
1194 ptr: &mut pid_v,
1195 },
1196 );
1197 let pid = if pid_v.is_int32() {
1198 pid_v.to_int32()
1199 } else {
1200 0
1201 };
1202 if pid == 0 {
1203 args.rval().set(BooleanValue(false));
1204 return true;
1205 }
1206
1207 let json_str = if argc > 0 {
1208 let v = *args.get(0).ptr;
1209 if v.is_string() {
1210 crate::js_to_rust_string(cx, v)
1211 } else {
1212 String::new()
1213 }
1214 } else {
1215 String::new()
1216 };
1217 let fd_opt: Option<i32> = if argc > 1 {
1218 let v = *args.get(1).ptr;
1219 if v.is_int32() {
1220 let n = v.to_int32();
1221 if n >= 0 {
1222 Some(n)
1223 } else {
1224 None
1225 }
1226 } else {
1227 None
1228 }
1229 } else {
1230 None
1231 };
1232
1233 let outcome: ::std::result::Result<(), String> =
1237 super::node_child_process::CP_IPC_CHANNELS
1238 .lock()
1239 .map_err(|e| format!("registry lock poisoned: {}", e))
1240 .and_then(|registry| {
1241 registry
1242 .get(&pid)
1243 .cloned()
1244 .ok_or_else(|| format!("no ipc channel for worker pid {}", pid))
1245 .and_then(|chan_mtx| {
1246 chan_mtx
1247 .lock()
1248 .map_err(|e| format!("channel lock poisoned: {}", e))
1249 .and_then(|mut chan| {
1250 if let Some(fd) = fd_opt {
1251 chan.send_handle(&json_str, fd)
1252 .map_err(|e| format!("send_handle: {}", e))
1253 } else {
1254 chan.send_json(&json_str)
1255 .map_err(|e| format!("send_json: {}", e))
1256 }
1257 })
1258 })
1259 });
1260
1261 match outcome {
1262 Ok(()) => {
1263 args.rval().set(BooleanValue(true));
1264 true
1265 }
1266 Err(msg) => {
1267 let c_msg = bun_core::ZBox::from_bytes(msg.as_bytes());
1268 JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
1269 args.rval().set(BooleanValue(false));
1270 false
1271 }
1272 }
1273}
1274
1275#[allow(unsafe_op_in_unsafe_fn)]
1281unsafe extern "C" fn cluster_worker_disconnect(
1282 cx: *mut JSContext,
1283 _argc: u32,
1284 vp: *mut JSVal,
1285) -> bool {
1286 let args = CallArgs::from_vp(vp, _argc);
1287
1288 let this_v = *args.thisv().ptr;
1290 let this_obj = if this_v.is_object() {
1291 this_v.to_object()
1292 } else {
1293 ::std::ptr::null_mut::<JSObject>()
1294 };
1295
1296 let mut wrapped_cx =
1297 mozjs::context::JSContext::from_ptr(::std::ptr::NonNull::new_unchecked(cx));
1298 let cx_ref = &mut wrapped_cx;
1299 rooted!(&in(cx_ref) let this_r = this_obj);
1300 let mut pid_v = UndefinedValue();
1301 JS_GetProperty(
1302 cx,
1303 this_r.handle().into(),
1304 c"_pid".as_ptr(),
1305 MutableHandle::<Value> {
1306 _phantom_0: ::std::marker::PhantomData,
1307 ptr: &mut pid_v,
1308 },
1309 );
1310 let pid = if pid_v.is_int32() {
1311 pid_v.to_int32()
1312 } else {
1313 0
1314 };
1315 if pid != 0 {
1316 if let Ok(mut registry) = super::node_child_process::CP_IPC_CHANNELS.lock() {
1317 registry.remove(&pid);
1318 }
1319 }
1320 args.rval().set(UndefinedValue());
1321 true
1322}
1323
1324#[allow(unsafe_op_in_unsafe_fn)]
1334unsafe extern "C" fn cluster_worker_boot(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
1335 let args = CallArgs::from_vp(vp, argc);
1336 let fd = if argc > 0 && (*args.get(0).ptr).is_int32() {
1337 (*args.get(0).ptr).to_int32()
1338 } else {
1339 3
1340 };
1341 if fd < 0 {
1342 args.rval().set(BooleanValue(false));
1343 return true;
1344 }
1345
1346 unsafe {
1355 let mut empty_mask: libc::sigset_t = ::std::mem::zeroed();
1356 libc::sigemptyset(&mut empty_mask);
1357 libc::sigprocmask(libc::SIG_SETMASK, &empty_mask, ::std::ptr::null_mut());
1358 }
1359
1360 let sock = unsafe {
1363 <::std::os::unix::net::UnixStream as ::std::os::unix::io::FromRawFd>::from_raw_fd(fd)
1364 };
1365 let channel = crate::ipc_channel::IpcChannel::new(sock);
1366 let self_pid = unsafe { libc::getpid() } as i32;
1367 if let Ok(mut registry) = super::node_child_process::CP_IPC_CHANNELS.lock() {
1368 registry.insert(self_pid, ::std::sync::Arc::new(::std::sync::Mutex::new(channel)));
1369 }
1370 args.rval().set(BooleanValue(true));
1371 true
1372}
1373
1374#[allow(unsafe_op_in_unsafe_fn)]
1383unsafe extern "C" fn cluster_ipc_send(cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
1384 let args = CallArgs::from_vp(vp, argc);
1385 let pid = if argc > 0 && (*args.get(0).ptr).is_int32() {
1386 (*args.get(0).ptr).to_int32()
1387 } else {
1388 0
1389 };
1390 if pid == 0 {
1391 args.rval().set(BooleanValue(false));
1392 return true;
1393 }
1394 let json_str = if argc > 1 && (*args.get(1).ptr).is_string() {
1395 crate::js_to_rust_string(cx, *args.get(1).ptr)
1396 } else {
1397 String::new()
1398 };
1399
1400 let outcome: ::std::result::Result<(), String> =
1401 super::node_child_process::CP_IPC_CHANNELS
1402 .lock()
1403 .map_err(|e| format!("registry lock poisoned: {}", e))
1404 .and_then(|registry| {
1405 registry
1406 .get(&pid)
1407 .cloned()
1408 .ok_or_else(|| format!("no ipc channel for pid {}", pid))
1409 .and_then(|chan_mtx| {
1410 chan_mtx
1411 .lock()
1412 .map_err(|e| format!("channel lock poisoned: {}", e))
1413 .and_then(|mut chan| {
1414 chan
1415 .send_json(&json_str)
1416 .map_err(|e| format!("send_json: {}", e))
1417 })
1418 })
1419 });
1420
1421 match outcome {
1422 Ok(()) => {
1423 args.rval().set(BooleanValue(true));
1424 true
1425 }
1426 Err(msg) => {
1427 let c_msg = bun_core::ZBox::from_bytes(msg.as_bytes());
1428 JS_ReportErrorUTF8(cx, c"%s".as_ptr(), c_msg.as_ptr());
1429 args.rval().set(BooleanValue(false));
1430 false
1431 }
1432 }
1433}
1434
1435#[allow(unsafe_op_in_unsafe_fn)]
1438unsafe extern "C" fn cluster_worker_kill(_cx: *mut JSContext, argc: u32, vp: *mut JSVal) -> bool {
1439 let args = CallArgs::from_vp(vp, argc);
1440 let pid = if argc > 0 && (*args.get(0).ptr).is_int32() {
1441 (*args.get(0).ptr).to_int32()
1442 } else {
1443 0
1444 };
1445 let sig = if argc > 1 && (*args.get(1).ptr).is_int32() {
1446 (*args.get(1).ptr).to_int32()
1447 } else {
1448 15 };
1450 if pid <= 0 {
1451 args.rval().set(BooleanValue(false));
1452 return true;
1453 }
1454 let rc = unsafe { libc::kill(pid, sig) };
1456 args.rval().set(BooleanValue(rc == 0));
1457 true
1458}
1459
1460const CLUSTER_JS: &str = r#"
1461(function() {
1462 var cluster = require('cluster');
1463 var cp = (function () { try { return require('child_process'); } catch (e) { return null; } })();
1464
1465 var SIG = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGABRT: 6, SIGKILL: 9, SIGUSR1: 10, SIGUSR2: 12, SIGTERM: 15 };
1466
1467 // Worker class with EventEmitter mixin.
1468 function Worker(id, process) {
1469 this.id = id;
1470 this.process = process;
1471 this.isConnected = true;
1472 this.isDead = false;
1473 this.exitedAfterDisconnect = false;
1474 this._events = {};
1475 this._onceFlags = {};
1476 this._online = false;
1477 this._disconnecting = false;
1478 }
1479
1480 Worker.prototype.on = function(event, cb) {
1481 if (!this._events[event]) this._events[event] = [];
1482 this._events[event].push(cb);
1483 return this;
1484 };
1485 Worker.prototype.once = function(event, cb) {
1486 this.on(event, cb);
1487 if (!this._onceFlags[event]) this._onceFlags[event] = [];
1488 this._onceFlags[event].push(this._events[event].length - 1);
1489 return this;
1490 };
1491 Worker.prototype.emit = function(event) {
1492 var args = Array.prototype.slice.call(arguments, 1);
1493 var cbs = this._events[event];
1494 if (!cbs || cbs.length === 0) return false;
1495 var onceIndices = this._onceFlags[event] || [];
1496 var remaining = [];
1497 for (var i = 0; i < cbs.length; i++) {
1498 try { cbs[i].apply(null, args); } catch(e) {}
1499 if (onceIndices.indexOf(i) < 0) remaining.push(cbs[i]);
1500 }
1501 this._events[event] = remaining;
1502 this._onceFlags[event] = [];
1503 return true;
1504 };
1505 Worker.prototype.removeListener = function(event, cb) {
1506 var cbs = this._events[event];
1507 if (!cbs) return this;
1508 var idx = cbs.indexOf(cb);
1509 if (idx >= 0) cbs.splice(idx, 1);
1510 return this;
1511 };
1512 Worker.prototype.removeAllListeners = function(event) {
1513 if (event) {
1514 delete this._events[event];
1515 } else {
1516 this._events = {};
1517 }
1518 return this;
1519 };
1520
1521 cluster._Worker = Worker;
1522
1523 // ─── Worker process boot: IPC wiring + process.send / 'message' ──────────
1524 if (cluster.isWorker && cp) {
1525 var fd = parseInt(process.env.BAO_CLUSTER_IPC_FD || '3', 10);
1526 var booted = typeof cluster.__cluster_worker_boot === 'function'
1527 && cluster.__cluster_worker_boot(fd);
1528 if (booted) {
1529 process.connected = true;
1530 process.send = function(message, sendHandle) {
1531 try { return cluster.__cluster_ipc_send(process.pid, JSON.stringify(message)); }
1532 catch (e) { return false; }
1533 };
1534 process.disconnect = function() {
1535 try { process.exit(0); } catch (e) {}
1536 };
1537 // Primary → worker message poll. BCE (parent-loop stall): this used to
1538 // be a `setInterval(..., 10)` that — never cleared or unref'd — pinned
1539 // the worker's event loop forever, so a worker whose script completed
1540 // never exited and the primary (waiting on that exit) never exited
1541 // either. The pump function below is driven by the native
1542 // cluster_pump_all from the drain hook and NEVER pins: a worker with a
1543 // drained loop exits (Node IPC-channel semantics).
1544 function workerIpcPump() {
1545 try {
1546 var m = cp.__cp_ipc_recv(process.pid);
1547 while (m && m.json) {
1548 var obj = null;
1549 try { obj = JSON.parse(m.json); } catch (e) { obj = null; }
1550 if (obj && obj.__cluster === 'disconnect') {
1551 process.exit(0);
1552 } else if (obj) {
1553 try { process.emit('message', obj); } catch (e) {}
1554 }
1555 m = cp.__cp_ipc_recv(process.pid);
1556 }
1557 // Primary closed the channel (disconnect) — exit gracefully.
1558 if (m && m.closed) {
1559 process.exit(0);
1560 }
1561 } catch (e) {}
1562 return false; // never pins the worker loop
1563 }
1564 if (typeof cluster.__cluster_pump_register === 'function') {
1565 cluster.__cluster_pump_register(workerIpcPump, false);
1566 }
1567 // Online handshake → primary emits worker 'online'.
1568 try {
1569 cluster.__cluster_ipc_send(process.pid, JSON.stringify({
1570 __cluster: 'online',
1571 workerId: process.env.BAO_CLUSTER_WORKER_ID
1572 }));
1573 } catch (e) {}
1574 }
1575 var workerId = parseInt(process.env.BAO_CLUSTER_WORKER_ID || '0', 10);
1576 cluster.worker = new Worker(workerId, process);
1577 }
1578
1579 // ─── Primary: wrap fork() results in Worker objects + event pump ─────────
1580 if (cluster.isPrimary) {
1581 var _originalFork = cluster.fork;
1582
1583 function dispatchMessage(w, json) {
1584 var obj = null;
1585 try { obj = JSON.parse(json); } catch (e) { return; }
1586 if (!obj || typeof obj !== 'object') return;
1587 if (obj.__cluster === 'online') {
1588 if (!w._online) {
1589 w._online = true;
1590 w.emit('online');
1591 cluster.emit('online', w);
1592 }
1593 return;
1594 }
1595 w.emit('message', obj);
1596 }
1597
1598 function handleExit(w, code, signal) {
1599 if (w.isDead) return;
1600 w.isDead = true;
1601 w.isConnected = false;
1602 w.exitedAfterDisconnect = !!w._disconnecting;
1603 if (w.process) w.process.exitCode = (code === -1 && signal) ? null : code;
1604 delete cluster.workers[w.id];
1605 try { if (typeof w.__disconnectNative === 'function') w.__disconnectNative(); } catch (e) {}
1606 w.emit('exit', code, signal);
1607 cluster.emit('exit', w, code, signal);
1608 }
1609
1610 // BCE (parent-loop stall): the old pollWorkers setInterval(10) was never
1611 // cleared while `cluster.workers` stayed non-empty — and because the
1612 // worker side never exited (see workerIpcPump note), the primary spun
1613 // forever. The pump function below is driven by the native
1614 // cluster_pump_all from the drain hook; its boolean return is the ONLY
1615 // loop-liveness contribution (true while any worker is registered).
1616 function pollWorkers() {
1617 var ids = Object.keys(cluster.workers);
1618 for (var i = 0; i < ids.length; i++) {
1619 var w = cluster.workers[ids[i]];
1620 if (!w || !w._pid) continue;
1621 if (cp) {
1622 try {
1623 var m = cp.__cp_ipc_recv(w._pid);
1624 while (m && m.json) {
1625 dispatchMessage(w, m.json);
1626 m = cp.__cp_ipc_recv(w._pid);
1627 }
1628 } catch (e) {}
1629 try {
1630 var ex = cp.__cp_poll_exit(w._pid);
1631 if (ex) handleExit(w, ex[0], ex[1]);
1632 } catch (e) {}
1633 }
1634 }
1635 return Object.keys(cluster.workers).length > 0;
1636 }
1637 if (typeof cluster.__cluster_pump_register === 'function') {
1638 cluster.__cluster_pump_register(pollWorkers, true);
1639 }
1640
1641 cluster.fork = function(env) {
1642 var result = _originalFork ? _originalFork.call(cluster, env) : null;
1643 if (result && result.id) {
1644 var worker = new Worker(result.id, result.process || result);
1645 worker._pid = result._pid || (result.process && result.process.pid) || 0;
1646 if (result.process) worker.process = result.process;
1647
1648 // Native bridges from the fork result object.
1649 if (typeof result.send === 'function') {
1650 var nativeSend = result.send;
1651 worker.send = function(message, sendHandle) {
1652 try { return nativeSend.call(result, JSON.stringify(message), sendHandle); }
1653 catch (e) { return false; }
1654 };
1655 }
1656 if (typeof result.disconnect === 'function') {
1657 var nativeDisconnect = result.disconnect;
1658 worker.__disconnectNative = function() { nativeDisconnect.call(result); };
1659 worker.disconnect = function() {
1660 worker._disconnecting = true;
1661 worker.isConnected = false;
1662 try { nativeDisconnect.call(result); } catch (e) {}
1663 };
1664 }
1665 // worker.kill([signal]) — Node semantics: SEND the signal; lifecycle
1666 // state (isDead / exitedAfterDisconnect / cluster.workers membership /
1667 // 'exit' event) is decided by the observed exit in handleExit, not
1668 // here. BCE (kill 永不达 exit): this used to set isDead=true
1669 // immediately, and handleExit early-returns on isDead — so the REAL
1670 // exit was dropped, 'exit' never fired, the worker stayed in
1671 // cluster.workers, and the primary's loop-liveness leak spun forever.
1672 worker.kill = function(signal) {
1673 var sig = typeof signal === 'number' ? signal : (SIG[String(signal).toUpperCase()] || 15);
1674 if (cluster.__cluster_worker_kill) {
1675 try { cluster.__cluster_worker_kill(worker._pid, sig); } catch (e) {}
1676 }
1677 if (sig === 9) return;
1678 // Grace escalation: SIGTERM is deliverable now (worker boot resets
1679 // the inherited all-blocked signal mask), but a worker wedged mid-
1680 // script must still die — after 1s, escalate to SIGKILL so
1681 // kill() ⇒ child dead ⇒ parent 'exit' is unconditional.
1682 var pid = worker._pid;
1683 setTimeout(function () {
1684 if (!worker.isDead && cluster.__cluster_worker_kill) {
1685 try { cluster.__cluster_worker_kill(pid, 9); } catch (e) {}
1686 }
1687 }, 1000);
1688 };
1689 worker.destroy = function(signal) { worker.kill(signal); };
1690
1691 if (!cluster.workers) cluster.workers = {};
1692 cluster.workers[result.id] = worker;
1693 cluster.emit('fork', worker);
1694 return worker;
1695 }
1696 return result;
1697 };
1698
1699 // Cluster-level EventEmitter.
1700 cluster._clusterEvents = {};
1701 cluster.on = function(event, cb) {
1702 if (!cluster._clusterEvents[event]) cluster._clusterEvents[event] = [];
1703 cluster._clusterEvents[event].push(cb);
1704 return cluster;
1705 };
1706 cluster.once = function(event, cb) {
1707 var wrap = function() {
1708 cluster.removeListener(event, wrap);
1709 cb.apply(null, arguments);
1710 };
1711 cluster.on(event, wrap);
1712 return cluster;
1713 };
1714 cluster.emit = function(event) {
1715 var args = Array.prototype.slice.call(arguments, 1);
1716 var cbs = cluster._clusterEvents[event];
1717 if (!cbs) return false;
1718 for (var i = 0; i < cbs.length; i++) {
1719 try { cbs[i].apply(null, args); } catch(e) {}
1720 }
1721 return true;
1722 };
1723 cluster.removeListener = function(event, cb) {
1724 var cbs = cluster._clusterEvents[event];
1725 if (!cbs) return cluster;
1726 var idx = cbs.indexOf(cb);
1727 if (idx >= 0) cbs.splice(idx, 1);
1728 return cluster;
1729 };
1730
1731 // cluster.disconnect(): ask every worker to exit (the worker exits on the
1732 // disconnect IPC message — bao's orderly-exit path can swallow SIGTERM),
1733 // close its channel, then SIGTERM as a backstop.
1734 cluster.disconnect = function(callback) {
1735 cluster._disconnecting = true;
1736 var ids = Object.keys(cluster.workers || {});
1737 for (var i = 0; i < ids.length; i++) {
1738 var w = cluster.workers[ids[i]];
1739 try { if (w.send) w.send({ __cluster: 'disconnect' }); } catch (e) {}
1740 try { if (w.disconnect) w.disconnect(); } catch (e) {}
1741 try { if (cluster.__cluster_worker_kill) cluster.__cluster_worker_kill(w._pid, 15); } catch (e) {}
1742 }
1743 if (typeof callback === 'function') {
1744 setTimeout(callback, 50);
1745 }
1746 };
1747
1748 // Initialize settings._nextId counter.
1749 if (!cluster.settings) cluster.settings = {};
1750 if (!cluster.settings._nextId) cluster.settings._nextId = 1;
1751 }
1752})();
1753"#;
1754
1755#[cfg(test)]
1756mod tests {
1757 use super::*;
1758
1759 #[test]
1760 fn test_is_cluster_worker_default() {
1761 assert!(!is_worker_env(None));
1764 }
1765
1766 #[test]
1767 fn test_is_cluster_worker_with_env() {
1768 assert!(is_worker_env(Some("1")));
1770 assert!(is_worker_env(Some("3")));
1771 assert!(!is_worker_env(Some("0")));
1773 }
1774
1775 #[test]
1776 fn test_is_cluster_worker_strict_predicate() {
1777 assert!(!is_worker_env(Some("")));
1780 assert!(!is_worker_env(Some("garbage")));
1781 assert!(!is_worker_env(Some("-1")));
1782 assert!(!is_worker_env(Some("1.5")));
1783 assert!(!is_worker_env(Some("1x")));
1784 }
1785
1786 #[test]
1787 fn test_is_primary_default() {
1788 assert!(!is_worker_env(None));
1790 }
1791}