1use std::sync::atomic::{AtomicBool, 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 #[cfg(feature = "jit")]
126 pub(crate) jit: Option<std::sync::Arc<crate::jit::JitRuntime>>,
127}
128
129pub struct Runtime {
140 shared: Arc<Shared>,
141 chunk: Arc<Chunk>,
142 natives: Arc<NativeTable>,
143 workers: Vec<JoinHandle<()>>,
144 timer_thread: Option<JoinHandle<()>>,
145 trust: crate::bytecode::TrustLevel,
146}
147
148impl Runtime {
149 pub fn new(chunk: Chunk) -> Result<Self, SpawnError> {
157 Self::with_config(chunk, RuntimeConfig::default())
158 }
159
160 pub fn with_natives(chunk: Chunk, natives: Arc<NativeTable>) -> Result<Self, SpawnError> {
163 Self::with_natives_and_config(chunk, natives, RuntimeConfig::default())
164 }
165
166 pub fn with_config(chunk: Chunk, config: RuntimeConfig) -> Result<Self, SpawnError> {
167 Self::with_natives_and_config(chunk, NativeTable::empty(), config)
168 }
169
170 pub fn with_std_natives_and_config(
173 chunk: Chunk,
174 config: RuntimeConfig,
175 ) -> Result<Self, SpawnError> {
176 Self::with_natives_and_config(
177 chunk,
178 crate::std_native_table_with(Arc::clone(&config.output)),
179 config,
180 )
181 }
182
183 pub fn with_natives_and_config(
191 chunk: Chunk,
192 natives: Arc<NativeTable>,
193 config: RuntimeConfig,
194 ) -> Result<Self, SpawnError> {
195 crate::bytecode::verify_with(
196 &chunk,
197 crate::bytecode::VerifyConfig {
198 trust: config.trust,
199 },
200 )
201 .map_err(SpawnError::VerifyFailed)?;
202 let chunk = Arc::new(chunk);
203 let workers_n = config.workers.max(1);
204
205 let locals: Vec<LocalDeque<Box<Flow>>> =
206 (0..workers_n).map(|_| LocalDeque::new_fifo()).collect();
207 let stealers: Vec<Stealer<Box<Flow>>> = locals.iter().map(|l| l.stealer()).collect();
208
209 #[cfg(feature = "jit")]
210 let jit = if config.jit.enabled {
211 Some(super::jit::new_runtime(chunk.clone(), config.jit.hot_threshold))
212 } else {
213 None
214 };
215
216 let shared = Arc::new(Shared {
217 injector: Injector::new(),
218 stealers,
219 directory: Directory::new(),
220 caps: super::capability::CapTable::new(),
221 timer: TimerWheel::new(),
222 notify: (Mutex::new(()), Condvar::new()),
223 metrics: RuntimeMetrics::default(),
224 shutdown: AtomicBool::new(false),
225 quantum: config.quantum,
226 mailbox: config.mailbox,
227 max_flows: config.max_flows,
228 quota: config.quota,
229 quotas: super::quota::QuotaTable::new(),
230 monitors: super::monitor::MonitorStore::new(),
231 links: super::link::LinkStore::new(),
232 registry: super::registry::RegistryStore::new(),
233 kill_signals: super::finalize::KillSignals::new(),
234 waiting_send_at: super::finalize::WaitingSendIndex::new(),
235 ask_waits: super::finalize::AskWaitIndex::new(),
236 #[cfg(feature = "jit")]
237 jit,
238 });
239
240 let mut workers = Vec::with_capacity(workers_n);
241 for local in locals {
242 let shared = shared.clone();
243 let handle = std::thread::Builder::new()
244 .name("byteflow-worker".into())
245 .spawn(move || worker::run_worker(shared, local))
246 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
247 workers.push(handle);
248 }
249
250 let shared_timer = shared.clone();
251 let timer_thread = std::thread::Builder::new()
252 .name("byteflow-timer".into())
253 .spawn(move || {
254 shared_timer
255 .timer
256 .clone()
257 .drive(
258 &shared_timer.injector,
259 &shared_timer.notify,
260 &shared_timer.ask_waits,
261 )
262 })
263 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
264
265 Ok(Runtime {
266 shared,
267 chunk,
268 natives,
269 workers,
270 timer_thread: Some(timer_thread),
271 trust: config.trust,
272 })
273 }
274
275 pub fn spawn(&self, function: u32, args: &[Value]) -> Result<FlowHandle, SpawnError> {
280 spawn_on(
281 &self.shared,
282 &self.chunk,
283 &self.natives,
284 function,
285 args,
286 RestartPolicy::Never,
287 None,
288 None,
289 None,
290 )
291 }
292
293 pub fn spawner(&self) -> RuntimeSpawner {
297 RuntimeSpawner { shared: self.shared.clone(), chunk: self.chunk.clone(), natives: self.natives.clone() }
298 }
299
300 pub fn supervisor(&self) -> Result<super::supervisor::Supervisor, SpawnError> {
303 super::supervisor::Supervisor::new(self.spawner())
304 }
305
306 pub fn function_index(&self, name: &str) -> Option<u32> {
310 self.chunk.functions.iter().position(|f| f.name == name).map(|i| i as u32)
311 }
312
313 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
314 self.shared.metrics.snapshot()
315 }
316
317 pub fn live_flows(&self) -> usize {
321 self.shared.directory.len()
322 }
323
324 pub fn worker_count(&self) -> usize {
325 self.workers.len()
326 }
327
328 pub fn send(&self, target: FlowId, message: Value) -> Result<(), SendError> {
339 if message.as_message().is_none() {
340 return Err(SendError::NotAHop {
341 got: message.type_name(),
342 });
343 }
344 let mailbox = match self.shared.directory.lookup(target) {
345 Ok(Some(m)) => m,
346 Ok(None) => return Err(SendError::NoSuchFlow(target)),
347 Err(e) => {
348 super::error::report_fault(e);
349 return Err(SendError::NoSuchFlow(target));
350 }
351 };
352 match mailbox.push(message.clone()) {
353 Ok(Ok(Delivery::Queued | Delivery::QueuedDropOldest | Delivery::DroppedNewest)) => {
354 Ok(())
355 }
356 Ok(Ok(Delivery::Handoff(mut flow))) => {
357 let _ = self.shared.ask_waits.remove_asker(flow.id);
358 if let Some(dest) = flow.last_receive_dest {
359 let _ = flow.vm.resume_with(dest, message);
360 }
361 self.shared.injector.push(flow);
362 wake_workers(&self.shared);
363 Ok(())
364 }
365 Ok(Err(full)) => Err(SendError::MailboxFull {
366 flow: target,
367 reason: full.reason(),
368 }),
369 Err(e) => {
370 super::error::report_fault(e);
371 Err(SendError::NoSuchFlow(target))
372 }
373 }
374 }
375
376 fn require_live(&self, id: FlowId) -> Result<(), super::error::LifecycleError> {
377 match self.shared.directory.lookup(id) {
378 Ok(Some(_)) => Ok(()),
379 Ok(None) => Err(super::error::LifecycleError::NoSuchFlow(id)),
380 Err(e) => Err(self.unavailable(e)),
381 }
382 }
383
384 fn unavailable(&self, err: super::error::RuntimeError) -> super::error::LifecycleError {
385 super::error::report_fault(err);
386 super::error::LifecycleError::Unavailable
387 }
388
389 pub fn mint_cap(&self, flow: FlowId) -> Result<crate::bytecode::CapId, super::error::LifecycleError> {
391 self.require_live(flow)?;
392 self.shared
393 .caps
394 .mint(flow, flow, super::capability::CapRights::ADDRESSING)
395 .map_err(|e| self.unavailable(e))
396 }
397
398 pub fn grant_cap(
400 &self,
401 holder: FlowId,
402 target: FlowId,
403 ) -> Result<crate::bytecode::CapId, super::error::LifecycleError> {
404 self.require_live(holder)?;
405 self.require_live(target)?;
406 self.shared
407 .caps
408 .mint(holder, target, super::capability::CapRights::ADDRESSING)
409 .map_err(|e| self.unavailable(e))
410 }
411
412 pub fn monitor(
416 &self,
417 owner: FlowId,
418 target: FlowId,
419 ) -> Result<super::monitor::MonitorRef, super::error::LifecycleError> {
420 if owner == target {
421 return Err(super::error::LifecycleError::SelfRelation);
422 }
423 self.require_live(owner)?;
424 self.require_live(target)?;
425 let mon = self
426 .shared
427 .monitors
428 .create(owner, target)
429 .map_err(|e| self.unavailable(e))?;
430 if self.require_live(target).is_err() {
433 let _ = self.shared.monitors.remove_owned(owner, mon);
434 super::finalize::deliver_down(
435 &self.shared,
436 super::monitor::DownEvent {
437 monitor: mon,
438 owner,
439 target,
440 reason: super::monitor::FlowExitReason::Fault,
441 },
442 );
443 }
444 Ok(mon)
445 }
446
447 pub fn demonitor(
449 &self,
450 owner: FlowId,
451 monitor: super::monitor::MonitorRef,
452 ) -> Result<(), super::error::LifecycleError> {
453 self.require_live(owner)?;
454 match self.shared.monitors.remove_owned(owner, monitor) {
455 Ok(inner) => inner,
456 Err(e) => Err(self.unavailable(e)),
457 }
458 }
459
460 pub fn link(
462 &self,
463 a: FlowId,
464 b: FlowId,
465 ) -> Result<super::link::LinkId, super::error::LifecycleError> {
466 if a == b {
467 return Err(super::error::LifecycleError::SelfRelation);
468 }
469 self.require_live(a)?;
470 self.require_live(b)?;
471 match self.shared.links.link(a, b) {
472 Ok(inner) => inner,
473 Err(e) => Err(self.unavailable(e)),
474 }
475 }
476
477 pub fn unlink(
479 &self,
480 owner: FlowId,
481 link: super::link::LinkId,
482 ) -> Result<(), super::error::LifecycleError> {
483 self.require_live(owner)?;
484 match self.shared.links.unlink_owned(owner, link) {
485 Ok(inner) => inner,
486 Err(e) => Err(self.unavailable(e)),
487 }
488 }
489
490 pub fn register_name(
492 &self,
493 name: &str,
494 cap: crate::bytecode::CapId,
495 ) -> Result<(), super::error::LifecycleError> {
496 let entry = match self.shared.caps.lookup(cap) {
497 Ok(Some(e)) => e,
498 Ok(None) => return Err(super::error::LifecycleError::InvalidCapability),
499 Err(e) => return Err(self.unavailable(e)),
500 };
501 let target = match entry.target() {
502 Some(id) => id,
503 None => return Err(super::error::LifecycleError::InvalidCapability),
504 };
505 self.require_live(target)?;
506 match self.shared.registry.register(
507 super::registry::RegistryName::from(name),
508 cap,
509 target,
510 ) {
511 Ok(inner) => inner,
512 Err(e) => Err(self.unavailable(e)),
513 }
514 }
515
516 pub fn whereis(&self, name: &str) -> Result<Option<crate::bytecode::CapId>, super::error::LifecycleError> {
518 self.shared
519 .registry
520 .whereis(name)
521 .map_err(|e| self.unavailable(e))
522 }
523
524 pub fn kill(&self, id: FlowId) -> Result<(), super::error::LifecycleError> {
527 self.require_live(id)?;
528 super::finalize::request_kill(&self.shared, id, super::monitor::FlowExitReason::Killed);
529 Ok(())
530 }
531
532 pub fn mint_admin_cap(
534 &self,
535 holder: FlowId,
536 ) -> Result<crate::bytecode::CapId, super::error::LifecycleError> {
537 self.require_live(holder)?;
538 let cap = crate::bytecode::Cap::root(
539 crate::bytecode::CapTarget::Scheduler,
540 CapRights::ADMIN,
541 None,
542 self.shared.caps.scheduler_cell().as_ref(),
543 );
544 self.shared
545 .caps
546 .grant(holder, cap)
547 .map_err(|e| self.unavailable(e))
548 }
549
550 pub fn admin_kill(
552 &self,
553 holder: FlowId,
554 cap: crate::bytecode::CapId,
555 target: FlowId,
556 ) -> Result<(), super::error::LifecycleError> {
557 self.require_admin(holder, cap)?;
558 self.kill(target)
559 }
560
561 pub fn admin_top_up_cpu(
563 &self,
564 holder: FlowId,
565 cap: crate::bytecode::CapId,
566 target: FlowId,
567 extra: i64,
568 ) -> Result<(), super::error::LifecycleError> {
569 self.require_admin(holder, cap)?;
570 let quota = match self.shared.quotas.get(target) {
571 Ok(Some(q)) => q,
572 Ok(None) => return Err(super::error::LifecycleError::NoSuchFlow(target)),
573 Err(e) => return Err(self.unavailable(e)),
574 };
575 quota.top_up_cpu(extra);
576 Ok(())
577 }
578
579 fn require_admin(
580 &self,
581 holder: FlowId,
582 cap: crate::bytecode::CapId,
583 ) -> Result<(), super::error::LifecycleError> {
584 let entry = match self.shared.caps.resolve(cap, holder, CapRights::ADMIN) {
585 Ok(e) => e,
586 Err(_) => return Err(super::error::LifecycleError::InvalidCapability),
587 };
588 super::link_admin::check_admin(&entry.cap, self.shared.caps.scheduler_cell().as_ref())
589 .map_err(|_| super::error::LifecycleError::InvalidCapability)
590 }
591
592 pub fn unregister_name(&self, name: &str) -> Result<bool, super::error::LifecycleError> {
594 self.shared
595 .registry
596 .unregister(name)
597 .map_err(|e| self.unavailable(e))
598 }
599
600 pub fn reload_chunk(&mut self, chunk: Chunk) -> Result<(), SpawnError> {
604 crate::bytecode::verify_with(
605 &chunk,
606 crate::bytecode::VerifyConfig {
607 trust: self.trust,
608 },
609 )
610 .map_err(SpawnError::VerifyFailed)?;
611 let chunk = Arc::new(chunk);
612 self.chunk = chunk.clone();
613 #[cfg(feature = "jit")]
614 if let Some(jit) = &self.shared.jit {
615 jit.reload(chunk);
616 }
617 Ok(())
618 }
619
620 fn request_shutdown(&self) {
623 self.shared.shutdown.store(true, Ordering::Release);
624 self.shared.timer.shutdown();
625 let (lock, cvar) = &self.shared.notify;
626 match super::sync_lock::lock(lock, "Runtime::request_shutdown") {
627 Ok(_g) => cvar.notify_all(),
628 Err(e) => super::error::report_fault(e),
629 }
630 }
631
632 pub fn shutdown(mut self) {
639 self.request_shutdown();
640 for w in self.workers.drain(..) {
641 let _ = w.join();
642 }
643 if let Some(t) = self.timer_thread.take() {
644 let _ = t.join();
645 }
646 }
647}
648
649impl Drop for Runtime {
650 fn drop(&mut self) {
651 if !self.shared.shutdown.load(Ordering::Acquire) {
652 self.request_shutdown();
653 }
654 }
655}
656
657pub fn flow_id_from_u64(raw: u64) -> FlowId {
661 FlowId(raw)
662}
663
664#[derive(Debug, Clone, PartialEq, Eq)]
666pub enum SendError {
667 NoSuchFlow(FlowId),
668 NotAHop { got: &'static str },
670 MailboxFull {
674 flow: FlowId,
675 reason: MailboxFullReason,
676 },
677}
678
679impl std::fmt::Display for SendError {
680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681 match self {
682 SendError::NoSuchFlow(id) => write!(f, "no live flow {id}"),
683 SendError::NotAHop { got } => {
684 write!(f, "atomic hop requires Value::Message, got {got}")
685 }
686 SendError::MailboxFull { flow, reason } => {
687 write!(f, "mailbox full for {flow} ({reason})")
688 }
689 }
690 }
691}
692
693impl std::error::Error for SendError {}
694
695pub(crate) struct BytecodeSpawn<'a> {
698 pub authority: &'a Cap,
699 pub cell: &'a crate::bytecode::RevocationCell,
700 pub quota: &'a super::quota::FlowQuota,
701 pub requested_rights: CapRights,
702}
703
704pub(crate) fn spawn_on(
713 shared: &Arc<Shared>,
714 chunk: &Arc<Chunk>,
715 natives: &Arc<NativeTable>,
716 function: u32,
717 args: &[Value],
718 restart_policy: RestartPolicy,
719 supervisor: Option<SupervisorLink>,
720 parent: Option<FlowId>,
721 bytecode: Option<BytecodeSpawn<'_>>,
722) -> Result<FlowHandle, SpawnError> {
723 if shared.max_flows > 0 {
724 let current = shared.directory.len();
725 if current >= shared.max_flows as usize {
726 return Err(SpawnError::FlowLimit {
727 current,
728 max: shared.max_flows,
729 });
730 }
731 }
732 let id = super::process::next_flow_id();
733 let cell = shared
734 .caps
735 .bind_flow(id)
736 .map_err(|_| SpawnError::Unavailable)?;
737 let authority = match bytecode {
738 None => Cap::root(
739 CapTarget::Flow(id.as_u64()),
740 CapRights::ROOT,
741 Some(NativeMask::full(natives.len())),
742 cell.as_ref(),
743 ),
744 Some(ctx) => super::spawn::exec_spawn_authority(
745 ctx.authority,
746 ctx.cell,
747 ctx.quota,
748 id.as_u64(),
749 ctx.requested_rights,
750 None,
751 cell.as_ref(),
752 )
753 .map_err(|e| SpawnError::SpawnDenied(e.to_string()))?,
754 };
755 let args = grant_caps_in_args(shared, parent, id, args)?;
756 let gate = NativeGate::from_authority(
757 &authority,
758 Arc::clone(&cell),
759 shared.caps.native_cell(),
760 natives.len(),
761 );
762 let vm = Vm::with_native_gate(chunk.clone(), natives.clone(), gate, function, args.as_slice())?;
763 let mailbox = Arc::new(Mailbox::with_config(shared.mailbox));
764 if let Err(e) = shared.directory.register(id, mailbox.clone()) {
765 super::error::report_fault(e);
766 return Err(SpawnError::Unavailable);
767 }
768 let quota = Arc::new(super::quota::FlowQuota::from_config(shared.quota));
769 if let Err(e) = shared.quotas.insert(id, Arc::clone("a)) {
770 super::error::report_fault(e);
771 return Err(SpawnError::Unavailable);
772 }
773 let (tx, rx) = super::oneshot::channel();
774 let mut flow = Box::new(Flow::new(id, vm, mailbox, restart_policy, tx));
775 flow.authority = authority;
776 flow.cell = cell;
777 flow.quota = quota;
778 flow.supervisor = supervisor;
779 RuntimeMetrics::inc(&shared.metrics.processes_spawned);
780 shared.injector.push(flow);
781 wake_workers(shared);
782 Ok(FlowHandle { id, receiver: rx })
783}
784
785fn grant_caps_in_args(
786 shared: &Shared,
787 parent: Option<FlowId>,
788 child: FlowId,
789 args: &[Value],
790) -> Result<Vec<Value>, SpawnError> {
791 let mut out = Vec::with_capacity(args.len());
792 for arg in args {
793 match arg {
794 Value::Cap(id) => {
795 let granted = match parent {
796 None => shared.caps.reissue_for(*id, child),
797 Some(p) => shared.caps.delegate(*id, p, child),
798 };
799 match granted {
800 Ok(cap) => out.push(Value::Cap(cap)),
801 Err(_) => return Err(SpawnError::InvalidCapability),
802 }
803 }
804 other => out.push(other.clone()),
805 }
806 }
807 Ok(out)
808}
809
810pub(crate) fn wake_workers(shared: &Shared) {
811 let (lock, cvar) = &shared.notify;
812 match super::sync_lock::lock(lock, "wake_workers") {
813 Ok(_g) => cvar.notify_one(),
814 Err(e) => super::error::report_fault(e),
815 }
816}
817
818#[derive(Clone)]
824pub struct RuntimeSpawner {
825 pub(crate) shared: Arc<Shared>,
826 pub(crate) chunk: Arc<Chunk>,
827 pub(crate) natives: Arc<NativeTable>,
828}
829
830impl RuntimeSpawner {
831 pub fn spawn(
832 &self,
833 function: u32,
834 args: &[Value],
835 restart_policy: RestartPolicy,
836 ) -> Result<FlowHandle, SpawnError> {
837 spawn_on(
838 &self.shared,
839 &self.chunk,
840 &self.natives,
841 function,
842 args,
843 restart_policy,
844 None,
845 None,
846 None,
847 )
848 }
849
850 pub(crate) fn spawn_linked(
851 &self,
852 function: u32,
853 args: &[Value],
854 restart_policy: RestartPolicy,
855 supervisor: SupervisorLink,
856 ) -> Result<FlowHandle, SpawnError> {
857 spawn_on(
858 &self.shared,
859 &self.chunk,
860 &self.natives,
861 function,
862 args,
863 restart_policy,
864 Some(supervisor),
865 None,
866 None,
867 )
868 }
869
870 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
871 self.shared.metrics.snapshot()
872 }
873
874 pub(crate) fn request_kill(&self, id: FlowId, reason: super::monitor::FlowExitReason) {
875 super::finalize::request_kill(&self.shared, id, reason);
876 }
877}
878
879#[cfg(test)]
880mod tests {
881 use super::*;
882 use crate::bytecode::{builder::ChunkBuilder, Opcode, Value};
883 use crate::scheduler::FlowOutcome;
884 use std::time::Duration;
885
886 fn add_chunk() -> Chunk {
887 let mut b = ChunkBuilder::new("test");
888 b.begin_function("main", 0, 2);
889 b.emit_load_imm(0, 41);
890 b.emit_load_imm(1, 1);
891 b.emit_binop(Opcode::Add, 0, 0, 1);
892 b.emit_return(0);
893 b.finish()
894 }
895
896 fn sleep_then_return_chunk(millis: i32) -> Chunk {
899 let mut b = ChunkBuilder::new("test");
900 b.begin_function("main", 0, 2);
901 b.emit_load_imm(0, millis);
902 b.emit_sleep(0);
903 b.emit_load_imm(0, 7);
904 b.emit_return(0);
905 b.finish()
906 }
907
908 #[test]
912 fn polling_and_bounded_waits_never_commit_the_host_thread() -> Result<(), Box<dyn std::error::Error>>
913 {
914 const FLOW_SLEEP: i32 = 150;
915 let rt = Runtime::with_config(
916 sleep_then_return_chunk(FLOW_SLEEP),
917 RuntimeConfig {
918 workers: 1,
919 quantum: 1_000,
920 mailbox: MailboxConfig::DEFAULT,
921 ..Default::default()
922 },
923 )?;
924 let handle = rt.spawn(0, &[])?;
925
926 if let Some(outcome) = handle.try_join() {
929 rt.shutdown();
930 return Err(format!("try_join answered too early: {outcome:?}").into());
931 }
932
933 if let Some(outcome) = handle.join_timeout(Duration::from_millis(20)) {
936 rt.shutdown();
937 return Err(format!("join_timeout answered too early: {outcome:?}").into());
938 }
939
940 let outcome = handle.join_timeout(Duration::from_secs(10));
943 rt.shutdown();
944 match outcome {
945 Some(FlowOutcome::Completed(Value::Int(7))) => Ok(()),
946 other => Err(format!("unexpected outcome: {other:?}").into()),
947 }
948 }
949
950 #[test]
951 fn spawn_and_join_add() -> Result<(), Box<dyn std::error::Error>> {
952 let rt = Runtime::with_config(
953 add_chunk(),
954 RuntimeConfig {
955 workers: 1,
956 quantum: 1_000,
957 mailbox: MailboxConfig::DEFAULT,
958 ..Default::default()
959 },
960 )?;
961 let outcome = rt.spawn(0, &[])?.join();
962 rt.shutdown();
963 match outcome {
964 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
965 other => Err(format!("unexpected outcome: {other:?}").into()),
966 }
967 }
968
969 fn receive_forever_chunk() -> Chunk {
970 let mut b = ChunkBuilder::new("recv");
971 b.begin_function("main", 0, 1);
972 b.emit_receive(0);
973 b.emit_return(0);
974 b.finish()
975 }
976
977 #[test]
978 fn kill_parked_flow_joins_failed() -> Result<(), Box<dyn std::error::Error>> {
979 let rt = Runtime::with_config(
980 receive_forever_chunk(),
981 RuntimeConfig {
982 workers: 1,
983 quantum: 1_000,
984 mailbox: MailboxConfig::DEFAULT,
985 ..Default::default()
986 },
987 )?;
988 let handle = rt.spawn(0, &[])?;
989 rt.kill(handle.id())?;
990 let outcome = handle.join();
991 rt.shutdown();
992 assert!(
993 matches!(outcome, FlowOutcome::Failed(_)),
994 "kill must fail the joiner, got {outcome:?}"
995 );
996 Ok(())
997 }
998
999 #[test]
1000 fn max_flows_rejects_extra_spawn() -> Result<(), Box<dyn std::error::Error>> {
1001 let rt = Runtime::with_config(
1002 receive_forever_chunk(),
1003 RuntimeConfig {
1004 workers: 1,
1005 quantum: 1_000,
1006 mailbox: MailboxConfig::DEFAULT,
1007 max_flows: 1,
1008 ..Default::default()
1009 },
1010 )?;
1011 let first = rt.spawn(0, &[])?;
1012 let second = rt.spawn(0, &[]);
1013 rt.kill(first.id())?;
1014 let _ = first.join();
1015 rt.shutdown();
1016 match second {
1017 Err(SpawnError::FlowLimit { current, max }) => {
1018 assert_eq!(current, 1);
1019 assert_eq!(max, 1);
1020 Ok(())
1021 }
1022 other => Err(format!(
1023 "expected FlowLimit, got {}",
1024 match &other {
1025 Ok(_) => "Ok(handle)".into(),
1026 Err(e) => format!("Err({e})"),
1027 }
1028 )
1029 .into()),
1030 }
1031 }
1032
1033 #[cfg(feature = "jit")]
1034 #[test]
1035 fn runtime_with_jit_enabled_completes_add() -> Result<(), Box<dyn std::error::Error>> {
1036 use crate::JitConfig;
1037
1038 let rt = Runtime::with_config(
1039 add_chunk(),
1040 RuntimeConfig {
1041 workers: 1,
1042 quantum: 1_000,
1043 mailbox: MailboxConfig::DEFAULT,
1044 jit: JitConfig {
1045 enabled: true,
1046 hot_threshold: 1,
1047 },
1048 ..Default::default()
1049 },
1050 )?;
1051 let outcome = rt.spawn(0, &[])?.join();
1052 rt.shutdown();
1053 match outcome {
1054 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
1055 other => Err(format!("unexpected outcome: {other:?}").into()),
1056 }
1057 }
1058}