1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
2use std::sync::{Arc, Condvar, Mutex};
3use std::thread::JoinHandle;
4
5use crate::bytecode::{Cap, CapRights, CapTarget, Chunk, NativeMask, Value};
6use crate::vm::{NativeGate, NativeTable, Vm};
7use crossbeam_deque::{Injector, Stealer, Worker as LocalDeque};
8
9use super::directory::Directory;
10use super::error::SpawnError;
11use super::handle::FlowHandle;
12use super::mailbox::{Delivery, Mailbox, MailboxConfig, MailboxFullReason};
13use super::metrics::{RuntimeMetrics, RuntimeMetricsSnapshot};
14use super::process::{Flow, FlowId, RestartPolicy};
15use super::supervisor::SupervisorLink;
16use super::timer::TimerWheel;
17use super::worker;
18
19pub const DEFAULT_QUANTUM: u32 = 10_000;
27
28#[derive(Clone, Debug)]
31pub struct RuntimeConfig {
32 pub workers: usize,
37 pub quantum: u32,
40 pub mailbox: MailboxConfig,
44 pub max_flows: u32,
47 pub trust: crate::bytecode::TrustLevel,
50 pub output: Arc<dyn crate::OutputSink>,
53 pub quota: super::quota::QuotaConfig,
55 #[cfg(feature = "jit")]
57 pub jit: JitConfig,
58}
59
60#[cfg(feature = "jit")]
62#[derive(Clone, Debug)]
63pub struct JitConfig {
64 pub enabled: bool,
66 pub hot_threshold: u32,
68}
69
70#[cfg(feature = "jit")]
71impl Default for JitConfig {
72 fn default() -> Self {
73 JitConfig {
74 enabled: false,
75 hot_threshold: crate::jit::HOT_THRESHOLD,
76 }
77 }
78}
79
80impl Default for RuntimeConfig {
81 fn default() -> Self {
82 RuntimeConfig {
83 workers: num_cpus::get().max(1),
84 quantum: DEFAULT_QUANTUM,
85 mailbox: MailboxConfig::DEFAULT,
86 max_flows: 0,
87 trust: crate::bytecode::TrustLevel::Untrusted,
88 output: Arc::new(crate::output::NullSink),
89 quota: super::quota::QuotaConfig::default(),
90 #[cfg(feature = "jit")]
91 jit: JitConfig::default(),
92 }
93 }
94}
95
96pub struct Shared {
103 pub(crate) injector: Injector<Box<Flow>>,
104 pub(crate) stealers: Vec<Stealer<Box<Flow>>>,
105 pub(crate) directory: Directory,
107 pub(crate) caps: super::capability::CapTable,
109 pub(crate) timer: Arc<TimerWheel>,
110 pub(crate) notify: (Mutex<()>, Condvar),
111 pub(crate) metrics: RuntimeMetrics,
112 pub(crate) shutdown: AtomicBool,
113 pub(crate) quantum: u32,
114 pub(crate) mailbox: MailboxConfig,
115 pub(crate) max_flows: u32,
116 pub(crate) quota: super::quota::QuotaConfig,
117 pub(crate) quotas: super::quota::QuotaTable,
118 pub(crate) monitors: super::monitor::MonitorStore,
119 pub(crate) links: super::link::LinkStore,
120 pub(crate) registry: super::registry::RegistryStore,
121 pub(crate) kill_signals: super::finalize::KillSignals,
122 pub(crate) waiting_send_at: super::finalize::WaitingSendIndex,
123 pub(crate) ask_waits: super::finalize::AskWaitIndex,
124 pub(crate) host_next_request_id: AtomicU64,
127 #[cfg(feature = "jit")]
129 pub(crate) jit: Option<std::sync::Arc<crate::jit::JitRuntime>>,
130}
131
132pub struct Runtime {
143 shared: Arc<Shared>,
144 chunk: Arc<Chunk>,
145 natives: Arc<NativeTable>,
146 workers: Vec<JoinHandle<()>>,
147 timer_thread: Option<JoinHandle<()>>,
148 trust: crate::bytecode::TrustLevel,
149}
150
151impl Runtime {
152 pub fn new(chunk: Chunk) -> Result<Self, SpawnError> {
160 Self::with_config(chunk, RuntimeConfig::default())
161 }
162
163 pub fn with_natives(chunk: Chunk, natives: Arc<NativeTable>) -> Result<Self, SpawnError> {
166 Self::with_natives_and_config(chunk, natives, RuntimeConfig::default())
167 }
168
169 pub fn with_config(chunk: Chunk, config: RuntimeConfig) -> Result<Self, SpawnError> {
170 Self::with_natives_and_config(chunk, NativeTable::empty(), config)
171 }
172
173 pub fn with_std_natives_and_config(
176 chunk: Chunk,
177 config: RuntimeConfig,
178 ) -> Result<Self, SpawnError> {
179 Self::with_natives_and_config(
180 chunk,
181 crate::std_native_table_with(Arc::clone(&config.output)),
182 config,
183 )
184 }
185
186 pub fn with_natives_and_config(
194 chunk: Chunk,
195 natives: Arc<NativeTable>,
196 config: RuntimeConfig,
197 ) -> Result<Self, SpawnError> {
198 crate::bytecode::verify_with(
199 &chunk,
200 crate::bytecode::VerifyConfig {
201 trust: config.trust,
202 },
203 )
204 .map_err(SpawnError::VerifyFailed)?;
205 let chunk = Arc::new(chunk);
206 let workers_n = config.workers.max(1);
207
208 let locals: Vec<LocalDeque<Box<Flow>>> =
209 (0..workers_n).map(|_| LocalDeque::new_fifo()).collect();
210 let stealers: Vec<Stealer<Box<Flow>>> = locals.iter().map(|l| l.stealer()).collect();
211
212 #[cfg(feature = "jit")]
213 let jit = if config.jit.enabled {
214 Some(super::jit::new_runtime(chunk.clone(), config.jit.hot_threshold))
215 } else {
216 None
217 };
218
219 let shared = Arc::new(Shared {
220 injector: Injector::new(),
221 stealers,
222 directory: Directory::new(),
223 caps: super::capability::CapTable::new(),
224 timer: TimerWheel::new(),
225 notify: (Mutex::new(()), Condvar::new()),
226 metrics: RuntimeMetrics::default(),
227 shutdown: AtomicBool::new(false),
228 quantum: config.quantum,
229 mailbox: config.mailbox,
230 max_flows: config.max_flows,
231 quota: config.quota,
232 quotas: super::quota::QuotaTable::new(),
233 monitors: super::monitor::MonitorStore::new(),
234 links: super::link::LinkStore::new(),
235 registry: super::registry::RegistryStore::new(),
236 kill_signals: super::finalize::KillSignals::new(),
237 waiting_send_at: super::finalize::WaitingSendIndex::new(),
238 ask_waits: super::finalize::AskWaitIndex::new(),
239 host_next_request_id: AtomicU64::new(1),
240 #[cfg(feature = "jit")]
241 jit,
242 });
243
244 let mut workers = Vec::with_capacity(workers_n);
245 for local in locals {
246 let shared = shared.clone();
247 let handle = std::thread::Builder::new()
248 .name("byteflow-worker".into())
249 .spawn(move || worker::run_worker(shared, local))
250 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
251 workers.push(handle);
252 }
253
254 let shared_timer = shared.clone();
255 let timer_thread = std::thread::Builder::new()
256 .name("byteflow-timer".into())
257 .spawn(move || {
258 shared_timer
259 .timer
260 .clone()
261 .drive(
262 &shared_timer.injector,
263 &shared_timer.notify,
264 &shared_timer.ask_waits,
265 )
266 })
267 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
268
269 Ok(Runtime {
270 shared,
271 chunk,
272 natives,
273 workers,
274 timer_thread: Some(timer_thread),
275 trust: config.trust,
276 })
277 }
278
279 pub fn spawn(&self, function: u32, args: &[Value]) -> Result<FlowHandle, SpawnError> {
284 spawn_on(
285 &self.shared,
286 &self.chunk,
287 &self.natives,
288 function,
289 args,
290 RestartPolicy::Never,
291 None,
292 None,
293 None,
294 )
295 }
296
297 pub fn spawner(&self) -> RuntimeSpawner {
301 RuntimeSpawner { shared: self.shared.clone(), chunk: self.chunk.clone(), natives: self.natives.clone() }
302 }
303
304 pub fn supervisor(&self) -> Result<super::supervisor::Supervisor, SpawnError> {
307 super::supervisor::Supervisor::new(self.spawner())
308 }
309
310 pub fn function_index(&self, name: &str) -> Option<u32> {
314 self.chunk.functions.iter().position(|f| f.name == name).map(|i| i as u32)
315 }
316
317 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
318 self.shared.metrics.snapshot()
319 }
320
321 pub fn live_flows(&self) -> usize {
325 self.shared.directory.len()
326 }
327
328 pub fn worker_count(&self) -> usize {
329 self.workers.len()
330 }
331
332 pub fn send(&self, target: FlowId, message: Value) -> Result<(), SendError> {
345 let Some(msg) = message.as_message().cloned() else {
346 return Err(SendError::NotAHop {
347 got: message.type_name(),
348 });
349 };
350 let mailbox = match self.shared.directory.lookup(target) {
351 Ok(Some(m)) => m,
352 Ok(None) => return Err(SendError::NoSuchFlow(target)),
353 Err(e) => {
354 super::error::report_fault(e);
355 return Err(SendError::NoSuchFlow(target));
356 }
357 };
358 let stamped = match worker::authenticate_host_outgoing_message(&self.shared, target, msg) {
359 Ok(m) => Value::Message(m),
360 Err(_) => return Err(SendError::Capability),
361 };
362 match mailbox.push(stamped.clone()) {
363 Ok(Ok(Delivery::Queued | Delivery::QueuedDropOldest | Delivery::DroppedNewest)) => {
364 Ok(())
365 }
366 Ok(Ok(Delivery::Handoff(mut flow))) => {
367 let _ = self.shared.ask_waits.remove_asker(flow.id);
368 if let Some(dest) = flow.last_receive_dest {
369 let _ = flow.vm.resume_with(dest, stamped);
370 }
371 self.shared.injector.push(flow);
372 wake_workers(&self.shared);
373 Ok(())
374 }
375 Ok(Err(full)) => Err(SendError::MailboxFull {
376 flow: target,
377 reason: full.reason(),
378 }),
379 Err(e) => {
380 super::error::report_fault(e);
381 Err(SendError::NoSuchFlow(target))
382 }
383 }
384 }
385
386 fn require_live(&self, id: FlowId) -> Result<(), super::error::LifecycleError> {
387 match self.shared.directory.lookup(id) {
388 Ok(Some(_)) => Ok(()),
389 Ok(None) => Err(super::error::LifecycleError::NoSuchFlow(id)),
390 Err(e) => Err(self.unavailable(e)),
391 }
392 }
393
394 fn unavailable(&self, err: super::error::RuntimeError) -> super::error::LifecycleError {
395 super::error::report_fault(err);
396 super::error::LifecycleError::Unavailable
397 }
398
399 pub fn mint_cap(&self, flow: FlowId) -> Result<crate::bytecode::CapId, super::error::LifecycleError> {
401 self.require_live(flow)?;
402 self.shared
403 .caps
404 .mint(flow, flow, super::capability::CapRights::ADDRESSING)
405 .map_err(|e| self.unavailable(e))
406 }
407
408 pub fn grant_cap(
410 &self,
411 holder: FlowId,
412 target: FlowId,
413 ) -> Result<crate::bytecode::CapId, super::error::LifecycleError> {
414 self.require_live(holder)?;
415 self.require_live(target)?;
416 self.shared
417 .caps
418 .mint(holder, target, super::capability::CapRights::ADDRESSING)
419 .map_err(|e| self.unavailable(e))
420 }
421
422 pub fn monitor(
426 &self,
427 owner: FlowId,
428 target: FlowId,
429 ) -> Result<super::monitor::MonitorRef, super::error::LifecycleError> {
430 if owner == target {
431 return Err(super::error::LifecycleError::SelfRelation);
432 }
433 self.require_live(owner)?;
434 self.require_live(target)?;
435 let mon = self
436 .shared
437 .monitors
438 .create(owner, target)
439 .map_err(|e| self.unavailable(e))?;
440 if self.require_live(target).is_err() {
443 let _ = self.shared.monitors.remove_owned(owner, mon);
444 super::finalize::deliver_down(
445 &self.shared,
446 super::monitor::DownEvent {
447 monitor: mon,
448 owner,
449 target,
450 reason: super::monitor::FlowExitReason::Fault,
451 },
452 );
453 }
454 Ok(mon)
455 }
456
457 pub fn demonitor(
459 &self,
460 owner: FlowId,
461 monitor: super::monitor::MonitorRef,
462 ) -> Result<(), super::error::LifecycleError> {
463 self.require_live(owner)?;
464 match self.shared.monitors.remove_owned(owner, monitor) {
465 Ok(inner) => inner,
466 Err(e) => Err(self.unavailable(e)),
467 }
468 }
469
470 pub fn link(
472 &self,
473 a: FlowId,
474 b: FlowId,
475 ) -> Result<super::link::LinkId, super::error::LifecycleError> {
476 if a == b {
477 return Err(super::error::LifecycleError::SelfRelation);
478 }
479 self.require_live(a)?;
480 self.require_live(b)?;
481 match self.shared.links.link(a, b) {
482 Ok(inner) => inner,
483 Err(e) => Err(self.unavailable(e)),
484 }
485 }
486
487 pub fn unlink(
489 &self,
490 owner: FlowId,
491 link: super::link::LinkId,
492 ) -> Result<(), super::error::LifecycleError> {
493 self.require_live(owner)?;
494 match self.shared.links.unlink_owned(owner, link) {
495 Ok(inner) => inner,
496 Err(e) => Err(self.unavailable(e)),
497 }
498 }
499
500 pub fn register_name(
502 &self,
503 name: &str,
504 cap: crate::bytecode::CapId,
505 ) -> Result<(), super::error::LifecycleError> {
506 let entry = match self.shared.caps.lookup(cap) {
507 Ok(Some(e)) => e,
508 Ok(None) => return Err(super::error::LifecycleError::InvalidCapability),
509 Err(e) => return Err(self.unavailable(e)),
510 };
511 let target = match entry.target() {
512 Some(id) => id,
513 None => return Err(super::error::LifecycleError::InvalidCapability),
514 };
515 self.require_live(target)?;
516 match self.shared.registry.register(
517 super::registry::RegistryName::from(name),
518 cap,
519 target,
520 ) {
521 Ok(inner) => inner,
522 Err(e) => Err(self.unavailable(e)),
523 }
524 }
525
526 pub fn whereis(&self, name: &str) -> Result<Option<crate::bytecode::CapId>, super::error::LifecycleError> {
528 self.shared
529 .registry
530 .whereis(name)
531 .map_err(|e| self.unavailable(e))
532 }
533
534 pub fn kill(&self, id: FlowId) -> Result<(), super::error::LifecycleError> {
537 self.require_live(id)?;
538 super::finalize::request_kill(&self.shared, id, super::monitor::FlowExitReason::Killed);
539 Ok(())
540 }
541
542 pub fn mint_admin_cap(
544 &self,
545 holder: FlowId,
546 ) -> Result<crate::bytecode::CapId, super::error::LifecycleError> {
547 self.require_live(holder)?;
548 let cap = crate::bytecode::Cap::root(
549 crate::bytecode::CapTarget::Scheduler,
550 CapRights::ADMIN,
551 None,
552 self.shared.caps.scheduler_cell().as_ref(),
553 );
554 self.shared
555 .caps
556 .grant(holder, cap)
557 .map_err(|e| self.unavailable(e))
558 }
559
560 pub fn admin_kill(
562 &self,
563 holder: FlowId,
564 cap: crate::bytecode::CapId,
565 target: FlowId,
566 ) -> Result<(), super::error::LifecycleError> {
567 self.require_admin(holder, cap)?;
568 self.kill(target)
569 }
570
571 pub fn admin_top_up_cpu(
573 &self,
574 holder: FlowId,
575 cap: crate::bytecode::CapId,
576 target: FlowId,
577 extra: i64,
578 ) -> Result<(), super::error::LifecycleError> {
579 self.require_admin(holder, cap)?;
580 let quota = match self.shared.quotas.get(target) {
581 Ok(Some(q)) => q,
582 Ok(None) => return Err(super::error::LifecycleError::NoSuchFlow(target)),
583 Err(e) => return Err(self.unavailable(e)),
584 };
585 quota.top_up_cpu(extra);
586 Ok(())
587 }
588
589 pub fn admin_top_up_mem(
591 &self,
592 holder: FlowId,
593 cap: crate::bytecode::CapId,
594 target: FlowId,
595 extra: usize,
596 ) -> Result<(), super::error::LifecycleError> {
597 self.require_admin(holder, cap)?;
598 let quota = match self.shared.quotas.get(target) {
599 Ok(Some(q)) => q,
600 Ok(None) => return Err(super::error::LifecycleError::NoSuchFlow(target)),
601 Err(e) => return Err(self.unavailable(e)),
602 };
603 quota.top_up_mem(extra);
604 Ok(())
605 }
606
607 pub fn admin_top_up_send(
609 &self,
610 holder: FlowId,
611 cap: crate::bytecode::CapId,
612 target: FlowId,
613 extra: i64,
614 ) -> Result<(), super::error::LifecycleError> {
615 self.require_admin(holder, cap)?;
616 let quota = match self.shared.quotas.get(target) {
617 Ok(Some(q)) => q,
618 Ok(None) => return Err(super::error::LifecycleError::NoSuchFlow(target)),
619 Err(e) => return Err(self.unavailable(e)),
620 };
621 quota.top_up_send(extra);
622 Ok(())
623 }
624
625 fn require_admin(
626 &self,
627 holder: FlowId,
628 cap: crate::bytecode::CapId,
629 ) -> Result<(), super::error::LifecycleError> {
630 let entry = match self.shared.caps.resolve(cap, holder, CapRights::ADMIN) {
631 Ok(e) => e,
632 Err(_) => return Err(super::error::LifecycleError::InvalidCapability),
633 };
634 super::link_admin::check_admin(&entry.cap, self.shared.caps.scheduler_cell().as_ref())
635 .map_err(|_| super::error::LifecycleError::InvalidCapability)
636 }
637
638 pub fn unregister_name(&self, name: &str) -> Result<bool, super::error::LifecycleError> {
640 self.shared
641 .registry
642 .unregister(name)
643 .map_err(|e| self.unavailable(e))
644 }
645
646 pub fn reload_chunk(&mut self, chunk: Chunk) -> Result<(), SpawnError> {
650 crate::bytecode::verify_with(
651 &chunk,
652 crate::bytecode::VerifyConfig {
653 trust: self.trust,
654 },
655 )
656 .map_err(SpawnError::VerifyFailed)?;
657 let chunk = Arc::new(chunk);
658 self.chunk = chunk.clone();
659 #[cfg(feature = "jit")]
660 if let Some(jit) = &self.shared.jit {
661 jit.reload(chunk);
662 }
663 Ok(())
664 }
665
666 fn request_shutdown(&self) {
669 self.shared.shutdown.store(true, Ordering::Release);
670 self.shared.timer.shutdown();
671 let (lock, cvar) = &self.shared.notify;
672 match super::sync_lock::lock(lock, "Runtime::request_shutdown") {
673 Ok(_g) => cvar.notify_all(),
674 Err(e) => super::error::report_fault(e),
675 }
676 }
677
678 pub fn shutdown(mut self) {
685 self.request_shutdown();
686 for w in self.workers.drain(..) {
687 let _ = w.join();
688 }
689 if let Some(t) = self.timer_thread.take() {
690 let _ = t.join();
691 }
692 }
693}
694
695impl Drop for Runtime {
696 fn drop(&mut self) {
697 if !self.shared.shutdown.load(Ordering::Acquire) {
698 self.request_shutdown();
699 }
700 }
701}
702
703pub fn flow_id_from_u64(raw: u64) -> FlowId {
707 FlowId(raw)
708}
709
710#[derive(Debug, Clone, PartialEq, Eq)]
712pub enum SendError {
713 NoSuchFlow(FlowId),
714 NotAHop { got: &'static str },
716 MailboxFull {
720 flow: FlowId,
721 reason: MailboxFullReason,
722 },
723 Capability,
725}
726
727impl std::fmt::Display for SendError {
728 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
729 match self {
730 SendError::NoSuchFlow(id) => write!(f, "no live flow {id}"),
731 SendError::NotAHop { got } => {
732 write!(f, "atomic hop requires Value::Message, got {got}")
733 }
734 SendError::MailboxFull { flow, reason } => {
735 write!(f, "mailbox full for {flow} ({reason})")
736 }
737 SendError::Capability => {
738 write!(f, "host send could not reissue a capability in the hop")
739 }
740 }
741 }
742}
743
744impl std::error::Error for SendError {}
745
746pub(crate) struct BytecodeSpawn<'a> {
749 pub authority: &'a Cap,
750 pub cell: &'a crate::bytecode::RevocationCell,
751 pub quota: &'a super::quota::FlowQuota,
752 pub requested_rights: CapRights,
753}
754
755pub(crate) fn spawn_on(
764 shared: &Arc<Shared>,
765 chunk: &Arc<Chunk>,
766 natives: &Arc<NativeTable>,
767 function: u32,
768 args: &[Value],
769 restart_policy: RestartPolicy,
770 supervisor: Option<SupervisorLink>,
771 parent: Option<FlowId>,
772 bytecode: Option<BytecodeSpawn<'_>>,
773) -> Result<FlowHandle, SpawnError> {
774 if shared.max_flows > 0 {
775 let current = shared.directory.len();
776 if current >= shared.max_flows as usize {
777 return Err(SpawnError::FlowLimit {
778 current,
779 max: shared.max_flows,
780 });
781 }
782 }
783 let id = super::process::next_flow_id();
784 let cell = shared
785 .caps
786 .bind_flow(id)
787 .map_err(|_| SpawnError::Unavailable)?;
788 let authority = match bytecode {
789 None => Cap::root(
790 CapTarget::Flow(id.as_u64()),
791 CapRights::ROOT,
792 Some(NativeMask::full(natives.len())),
793 cell.as_ref(),
794 ),
795 Some(ctx) => super::spawn::exec_spawn_authority(
796 ctx.authority,
797 ctx.cell,
798 ctx.quota,
799 id.as_u64(),
800 ctx.requested_rights,
801 None,
802 cell.as_ref(),
803 )
804 .map_err(|e| SpawnError::SpawnDenied(e.to_string()))?,
805 };
806 let args = grant_caps_in_args(shared, parent, id, args)?;
807 let gate = NativeGate::from_authority(
808 &authority,
809 Arc::clone(&cell),
810 shared.caps.native_cell(),
811 natives.len(),
812 );
813 let quota = Arc::new(super::quota::FlowQuota::from_config(shared.quota));
814 let mut vm = Vm::with_native_gate(chunk.clone(), natives.clone(), gate, function, args.as_slice())?;
815 vm.set_quota(Arc::clone("a))?;
816 let mailbox = Arc::new(Mailbox::with_config(shared.mailbox));
817 if let Err(e) = shared.directory.register(id, mailbox.clone()) {
818 super::error::report_fault(e);
819 return Err(SpawnError::Unavailable);
820 }
821 if let Err(e) = shared.quotas.insert(id, Arc::clone("a)) {
822 super::error::report_fault(e);
823 return Err(SpawnError::Unavailable);
824 }
825 let (tx, rx) = super::oneshot::channel();
826 let mut flow = Box::new(Flow::new(id, vm, mailbox, restart_policy, tx));
827 flow.authority = authority;
828 flow.cell = cell;
829 flow.quota = quota;
830 flow.supervisor = supervisor;
831 RuntimeMetrics::inc(&shared.metrics.processes_spawned);
832 shared.injector.push(flow);
833 wake_workers(shared);
834 Ok(FlowHandle { id, receiver: rx })
835}
836
837fn grant_caps_in_args(
838 shared: &Shared,
839 parent: Option<FlowId>,
840 child: FlowId,
841 args: &[Value],
842) -> Result<Vec<Value>, SpawnError> {
843 let mut out = Vec::with_capacity(args.len());
844 for arg in args {
845 match arg {
846 Value::Cap(id) => {
847 let granted = match parent {
848 None => shared.caps.reissue_for(*id, child),
849 Some(p) => shared.caps.delegate(*id, p, child),
850 };
851 match granted {
852 Ok(cap) => out.push(Value::Cap(cap)),
853 Err(_) => return Err(SpawnError::InvalidCapability),
854 }
855 }
856 other => out.push(other.clone()),
857 }
858 }
859 Ok(out)
860}
861
862pub(crate) fn wake_workers(shared: &Shared) {
863 let (lock, cvar) = &shared.notify;
864 match super::sync_lock::lock(lock, "wake_workers") {
865 Ok(_g) => cvar.notify_one(),
866 Err(e) => super::error::report_fault(e),
867 }
868}
869
870#[derive(Clone)]
876pub struct RuntimeSpawner {
877 pub(crate) shared: Arc<Shared>,
878 pub(crate) chunk: Arc<Chunk>,
879 pub(crate) natives: Arc<NativeTable>,
880}
881
882impl RuntimeSpawner {
883 pub fn spawn(
884 &self,
885 function: u32,
886 args: &[Value],
887 restart_policy: RestartPolicy,
888 ) -> Result<FlowHandle, SpawnError> {
889 spawn_on(
890 &self.shared,
891 &self.chunk,
892 &self.natives,
893 function,
894 args,
895 restart_policy,
896 None,
897 None,
898 None,
899 )
900 }
901
902 pub(crate) fn spawn_linked(
903 &self,
904 function: u32,
905 args: &[Value],
906 restart_policy: RestartPolicy,
907 supervisor: SupervisorLink,
908 ) -> Result<FlowHandle, SpawnError> {
909 spawn_on(
910 &self.shared,
911 &self.chunk,
912 &self.natives,
913 function,
914 args,
915 restart_policy,
916 Some(supervisor),
917 None,
918 None,
919 )
920 }
921
922 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
923 self.shared.metrics.snapshot()
924 }
925
926 pub(crate) fn request_kill(&self, id: FlowId, reason: super::monitor::FlowExitReason) {
927 super::finalize::request_kill(&self.shared, id, reason);
928 }
929}
930
931#[cfg(test)]
932mod tests {
933 use super::*;
934 use crate::bytecode::{builder::ChunkBuilder, Opcode, Value};
935 use crate::scheduler::FlowOutcome;
936 use std::time::Duration;
937
938 fn add_chunk() -> Chunk {
939 let mut b = ChunkBuilder::new("test");
940 b.begin_function("main", 0, 2);
941 b.emit_load_imm(0, 41);
942 b.emit_load_imm(1, 1);
943 b.emit_binop(Opcode::Add, 0, 0, 1);
944 b.emit_return(0);
945 b.finish()
946 }
947
948 fn sleep_then_return_chunk(millis: i32) -> Chunk {
951 let mut b = ChunkBuilder::new("test");
952 b.begin_function("main", 0, 2);
953 b.emit_load_imm(0, millis);
954 b.emit_sleep(0);
955 b.emit_load_imm(0, 7);
956 b.emit_return(0);
957 b.finish()
958 }
959
960 #[test]
964 fn polling_and_bounded_waits_never_commit_the_host_thread() -> Result<(), Box<dyn std::error::Error>>
965 {
966 const FLOW_SLEEP: i32 = 150;
967 let rt = Runtime::with_config(
968 sleep_then_return_chunk(FLOW_SLEEP),
969 RuntimeConfig {
970 workers: 1,
971 quantum: 1_000,
972 mailbox: MailboxConfig::DEFAULT,
973 ..Default::default()
974 },
975 )?;
976 let handle = rt.spawn(0, &[])?;
977
978 if let Some(outcome) = handle.try_join() {
981 rt.shutdown();
982 return Err(format!("try_join answered too early: {outcome:?}").into());
983 }
984
985 if let Some(outcome) = handle.join_timeout(Duration::from_millis(20)) {
988 rt.shutdown();
989 return Err(format!("join_timeout answered too early: {outcome:?}").into());
990 }
991
992 let outcome = handle.join_timeout(Duration::from_secs(10));
995 rt.shutdown();
996 match outcome {
997 Some(FlowOutcome::Completed(Value::Int(7))) => Ok(()),
998 other => Err(format!("unexpected outcome: {other:?}").into()),
999 }
1000 }
1001
1002 #[test]
1003 fn spawn_and_join_add() -> Result<(), Box<dyn std::error::Error>> {
1004 let rt = Runtime::with_config(
1005 add_chunk(),
1006 RuntimeConfig {
1007 workers: 1,
1008 quantum: 1_000,
1009 mailbox: MailboxConfig::DEFAULT,
1010 ..Default::default()
1011 },
1012 )?;
1013 let outcome = rt.spawn(0, &[])?.join();
1014 rt.shutdown();
1015 match outcome {
1016 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
1017 other => Err(format!("unexpected outcome: {other:?}").into()),
1018 }
1019 }
1020
1021 fn receive_forever_chunk() -> Chunk {
1022 let mut b = ChunkBuilder::new("recv");
1023 b.begin_function("main", 0, 1);
1024 b.emit_receive(0);
1025 b.emit_return(0);
1026 b.finish()
1027 }
1028
1029 #[test]
1030 fn kill_parked_flow_joins_failed() -> Result<(), Box<dyn std::error::Error>> {
1031 let rt = Runtime::with_config(
1032 receive_forever_chunk(),
1033 RuntimeConfig {
1034 workers: 1,
1035 quantum: 1_000,
1036 mailbox: MailboxConfig::DEFAULT,
1037 ..Default::default()
1038 },
1039 )?;
1040 let handle = rt.spawn(0, &[])?;
1041 rt.kill(handle.id())?;
1042 let outcome = handle.join();
1043 rt.shutdown();
1044 assert!(
1045 matches!(outcome, FlowOutcome::Failed(_)),
1046 "kill must fail the joiner, got {outcome:?}"
1047 );
1048 Ok(())
1049 }
1050
1051 #[test]
1052 fn max_flows_rejects_extra_spawn() -> Result<(), Box<dyn std::error::Error>> {
1053 let rt = Runtime::with_config(
1054 receive_forever_chunk(),
1055 RuntimeConfig {
1056 workers: 1,
1057 quantum: 1_000,
1058 mailbox: MailboxConfig::DEFAULT,
1059 max_flows: 1,
1060 ..Default::default()
1061 },
1062 )?;
1063 let first = rt.spawn(0, &[])?;
1064 let second = rt.spawn(0, &[]);
1065 rt.kill(first.id())?;
1066 let _ = first.join();
1067 rt.shutdown();
1068 match second {
1069 Err(SpawnError::FlowLimit { current, max }) => {
1070 assert_eq!(current, 1);
1071 assert_eq!(max, 1);
1072 Ok(())
1073 }
1074 other => Err(format!(
1075 "expected FlowLimit, got {}",
1076 match &other {
1077 Ok(_) => "Ok(handle)".into(),
1078 Err(e) => format!("Err({e})"),
1079 }
1080 )
1081 .into()),
1082 }
1083 }
1084
1085 #[cfg(feature = "jit")]
1086 #[test]
1087 fn runtime_with_jit_enabled_completes_add() -> Result<(), Box<dyn std::error::Error>> {
1088 use crate::JitConfig;
1089
1090 let rt = Runtime::with_config(
1091 add_chunk(),
1092 RuntimeConfig {
1093 workers: 1,
1094 quantum: 1_000,
1095 mailbox: MailboxConfig::DEFAULT,
1096 jit: JitConfig {
1097 enabled: true,
1098 hot_threshold: 1,
1099 },
1100 ..Default::default()
1101 },
1102 )?;
1103 let outcome = rt.spawn(0, &[])?.join();
1104 rt.shutdown();
1105 match outcome {
1106 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
1107 other => Err(format!("unexpected outcome: {other:?}").into()),
1108 }
1109 }
1110}