1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::{Arc, Condvar, Mutex};
3use std::thread::JoinHandle;
4
5use crate::bytecode::{Chunk, Value};
6use crate::vm::{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 #[cfg(feature = "jit")]
49 pub jit: JitConfig,
50}
51
52#[cfg(feature = "jit")]
54#[derive(Clone, Debug)]
55pub struct JitConfig {
56 pub enabled: bool,
58 pub hot_threshold: u32,
60}
61
62#[cfg(feature = "jit")]
63impl Default for JitConfig {
64 fn default() -> Self {
65 JitConfig {
66 enabled: false,
67 hot_threshold: crate::jit::HOT_THRESHOLD,
68 }
69 }
70}
71
72impl Default for RuntimeConfig {
73 fn default() -> Self {
74 RuntimeConfig {
75 workers: num_cpus::get().max(1),
76 quantum: DEFAULT_QUANTUM,
77 mailbox: MailboxConfig::DEFAULT,
78 max_flows: 0,
79 #[cfg(feature = "jit")]
80 jit: JitConfig::default(),
81 }
82 }
83}
84
85pub struct Shared {
92 pub(crate) injector: Injector<Box<Flow>>,
93 pub(crate) stealers: Vec<Stealer<Box<Flow>>>,
94 pub(crate) directory: Directory,
96 pub(crate) caps: super::capability::CapTable,
98 pub(crate) timer: Arc<TimerWheel>,
99 pub(crate) notify: (Mutex<()>, Condvar),
100 pub(crate) metrics: RuntimeMetrics,
101 pub(crate) shutdown: AtomicBool,
102 pub(crate) quantum: u32,
103 pub(crate) mailbox: MailboxConfig,
104 pub(crate) max_flows: u32,
105 pub(crate) monitors: super::monitor::MonitorStore,
106 pub(crate) links: super::link::LinkStore,
107 pub(crate) registry: super::registry::RegistryStore,
108 pub(crate) kill_signals: super::finalize::KillSignals,
109 pub(crate) waiting_send_at: super::finalize::WaitingSendIndex,
110 pub(crate) ask_waits: super::finalize::AskWaitIndex,
111 #[cfg(feature = "jit")]
113 pub(crate) jit: Option<std::sync::Arc<crate::jit::JitRuntime>>,
114}
115
116pub struct Runtime {
127 shared: Arc<Shared>,
128 chunk: Arc<Chunk>,
129 natives: Arc<NativeTable>,
130 workers: Vec<JoinHandle<()>>,
131 timer_thread: Option<JoinHandle<()>>,
132}
133
134impl Runtime {
135 pub fn new(chunk: Chunk) -> Result<Self, SpawnError> {
143 Self::with_config(chunk, RuntimeConfig::default())
144 }
145
146 pub fn with_natives(chunk: Chunk, natives: Arc<NativeTable>) -> Result<Self, SpawnError> {
149 Self::with_natives_and_config(chunk, natives, RuntimeConfig::default())
150 }
151
152 pub fn with_config(chunk: Chunk, config: RuntimeConfig) -> Result<Self, SpawnError> {
153 Self::with_natives_and_config(chunk, NativeTable::empty(), config)
154 }
155
156 pub fn with_natives_and_config(
164 chunk: Chunk,
165 natives: Arc<NativeTable>,
166 config: RuntimeConfig,
167 ) -> Result<Self, SpawnError> {
168 crate::bytecode::verify(&chunk).map_err(|e| SpawnError::VerifyFailed(e.to_string()))?;
169 let chunk = Arc::new(chunk);
170 let workers_n = config.workers.max(1);
171
172 let locals: Vec<LocalDeque<Box<Flow>>> =
173 (0..workers_n).map(|_| LocalDeque::new_fifo()).collect();
174 let stealers: Vec<Stealer<Box<Flow>>> = locals.iter().map(|l| l.stealer()).collect();
175
176 #[cfg(feature = "jit")]
177 let jit = if config.jit.enabled {
178 Some(super::jit::new_runtime(chunk.clone(), config.jit.hot_threshold))
179 } else {
180 None
181 };
182
183 let shared = Arc::new(Shared {
184 injector: Injector::new(),
185 stealers,
186 directory: Directory::new(),
187 caps: super::capability::CapTable::new(),
188 timer: TimerWheel::new(),
189 notify: (Mutex::new(()), Condvar::new()),
190 metrics: RuntimeMetrics::default(),
191 shutdown: AtomicBool::new(false),
192 quantum: config.quantum,
193 mailbox: config.mailbox,
194 max_flows: config.max_flows,
195 monitors: super::monitor::MonitorStore::new(),
196 links: super::link::LinkStore::new(),
197 registry: super::registry::RegistryStore::new(),
198 kill_signals: super::finalize::KillSignals::new(),
199 waiting_send_at: super::finalize::WaitingSendIndex::new(),
200 ask_waits: super::finalize::AskWaitIndex::new(),
201 #[cfg(feature = "jit")]
202 jit,
203 });
204
205 let mut workers = Vec::with_capacity(workers_n);
206 for local in locals {
207 let shared = shared.clone();
208 let handle = std::thread::Builder::new()
209 .name("byteflow-worker".into())
210 .spawn(move || worker::run_worker(shared, local))
211 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
212 workers.push(handle);
213 }
214
215 let shared_timer = shared.clone();
216 let timer_thread = std::thread::Builder::new()
217 .name("byteflow-timer".into())
218 .spawn(move || {
219 shared_timer
220 .timer
221 .clone()
222 .drive(
223 &shared_timer.injector,
224 &shared_timer.notify,
225 &shared_timer.ask_waits,
226 )
227 })
228 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
229
230 Ok(Runtime {
231 shared,
232 chunk,
233 natives,
234 workers,
235 timer_thread: Some(timer_thread),
236 })
237 }
238
239 pub fn spawn(&self, function: u32, args: &[Value]) -> Result<FlowHandle, SpawnError> {
244 spawn_on(
245 &self.shared,
246 &self.chunk,
247 &self.natives,
248 function,
249 args,
250 RestartPolicy::Never,
251 None,
252 )
253 }
254
255 pub fn spawner(&self) -> RuntimeSpawner {
259 RuntimeSpawner { shared: self.shared.clone(), chunk: self.chunk.clone(), natives: self.natives.clone() }
260 }
261
262 pub fn supervisor(&self) -> Result<super::supervisor::Supervisor, SpawnError> {
265 super::supervisor::Supervisor::new(self.spawner())
266 }
267
268 pub fn function_index(&self, name: &str) -> Option<u32> {
272 self.chunk.functions.iter().position(|f| f.name == name).map(|i| i as u32)
273 }
274
275 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
276 self.shared.metrics.snapshot()
277 }
278
279 pub fn live_flows(&self) -> usize {
283 self.shared.directory.len()
284 }
285
286 pub fn worker_count(&self) -> usize {
287 self.workers.len()
288 }
289
290 pub fn send(&self, target: FlowId, message: Value) -> Result<(), SendError> {
301 if message.as_message().is_none() {
302 return Err(SendError::NotAHop {
303 got: message.type_name(),
304 });
305 }
306 let mailbox = match self.shared.directory.lookup(target) {
307 Ok(Some(m)) => m,
308 Ok(None) => return Err(SendError::NoSuchFlow(target)),
309 Err(e) => {
310 super::error::report_fault(e);
311 return Err(SendError::NoSuchFlow(target));
312 }
313 };
314 match mailbox.push(message.clone()) {
315 Ok(Ok(Delivery::Queued | Delivery::QueuedDropOldest | Delivery::DroppedNewest)) => {
316 Ok(())
317 }
318 Ok(Ok(Delivery::Handoff(mut flow))) => {
319 let _ = self.shared.ask_waits.remove_asker(flow.id);
320 if let Some(dest) = flow.last_receive_dest {
321 let _ = flow.vm.resume_with(dest, message);
322 }
323 self.shared.injector.push(flow);
324 wake_workers(&self.shared);
325 Ok(())
326 }
327 Ok(Err(full)) => Err(SendError::MailboxFull {
328 flow: target,
329 reason: full.reason(),
330 }),
331 Err(e) => {
332 super::error::report_fault(e);
333 Err(SendError::NoSuchFlow(target))
334 }
335 }
336 }
337
338 fn require_live(&self, id: FlowId) -> Result<(), super::error::LifecycleError> {
339 match self.shared.directory.lookup(id) {
340 Ok(Some(_)) => Ok(()),
341 Ok(None) => Err(super::error::LifecycleError::NoSuchFlow(id)),
342 Err(e) => Err(self.unavailable(e)),
343 }
344 }
345
346 fn unavailable(&self, err: super::error::RuntimeError) -> super::error::LifecycleError {
347 super::error::report_fault(err);
348 super::error::LifecycleError::Unavailable
349 }
350
351 pub fn mint_cap(&self, flow: FlowId) -> Result<super::capability::CapId, super::error::LifecycleError> {
353 self.require_live(flow)?;
354 self.shared
355 .caps
356 .mint(flow, super::capability::CapRights::SEND_ASK)
357 .map_err(|e| self.unavailable(e))
358 }
359
360 pub fn monitor(
364 &self,
365 owner: FlowId,
366 target: FlowId,
367 ) -> Result<super::monitor::MonitorRef, super::error::LifecycleError> {
368 if owner == target {
369 return Err(super::error::LifecycleError::SelfRelation);
370 }
371 self.require_live(owner)?;
372 self.require_live(target)?;
373 let mon = self
374 .shared
375 .monitors
376 .create(owner, target)
377 .map_err(|e| self.unavailable(e))?;
378 if self.require_live(target).is_err() {
381 let _ = self.shared.monitors.remove_owned(owner, mon);
382 super::finalize::deliver_down(
383 &self.shared,
384 super::monitor::DownEvent {
385 monitor: mon,
386 owner,
387 target,
388 reason: super::monitor::FlowExitReason::Fault,
389 },
390 );
391 }
392 Ok(mon)
393 }
394
395 pub fn demonitor(
397 &self,
398 owner: FlowId,
399 monitor: super::monitor::MonitorRef,
400 ) -> Result<(), super::error::LifecycleError> {
401 self.require_live(owner)?;
402 match self.shared.monitors.remove_owned(owner, monitor) {
403 Ok(inner) => inner,
404 Err(e) => Err(self.unavailable(e)),
405 }
406 }
407
408 pub fn link(
410 &self,
411 a: FlowId,
412 b: FlowId,
413 ) -> Result<super::link::LinkId, super::error::LifecycleError> {
414 if a == b {
415 return Err(super::error::LifecycleError::SelfRelation);
416 }
417 self.require_live(a)?;
418 self.require_live(b)?;
419 match self.shared.links.link(a, b) {
420 Ok(inner) => inner,
421 Err(e) => Err(self.unavailable(e)),
422 }
423 }
424
425 pub fn unlink(
427 &self,
428 owner: FlowId,
429 link: super::link::LinkId,
430 ) -> Result<(), super::error::LifecycleError> {
431 self.require_live(owner)?;
432 match self.shared.links.unlink_owned(owner, link) {
433 Ok(inner) => inner,
434 Err(e) => Err(self.unavailable(e)),
435 }
436 }
437
438 pub fn register_name(
440 &self,
441 name: &str,
442 cap: super::capability::CapId,
443 ) -> Result<(), super::error::LifecycleError> {
444 let entry = match self.shared.caps.resolve(cap) {
445 Ok(Some(e)) => e,
446 Ok(None) => return Err(super::error::LifecycleError::InvalidCapability),
447 Err(e) => return Err(self.unavailable(e)),
448 };
449 self.require_live(entry.flow)?;
450 match self.shared.registry.register(
451 super::registry::RegistryName::from(name),
452 cap,
453 entry.flow,
454 ) {
455 Ok(inner) => inner,
456 Err(e) => Err(self.unavailable(e)),
457 }
458 }
459
460 pub fn whereis(&self, name: &str) -> Result<Option<super::capability::CapId>, super::error::LifecycleError> {
462 self.shared
463 .registry
464 .whereis(name)
465 .map_err(|e| self.unavailable(e))
466 }
467
468 pub fn kill(&self, id: FlowId) -> Result<(), super::error::LifecycleError> {
471 self.require_live(id)?;
472 super::finalize::request_kill(&self.shared, id, super::monitor::FlowExitReason::Killed);
473 Ok(())
474 }
475
476 pub fn unregister_name(&self, name: &str) -> Result<bool, super::error::LifecycleError> {
478 self.shared
479 .registry
480 .unregister(name)
481 .map_err(|e| self.unavailable(e))
482 }
483
484 pub fn reload_chunk(&mut self, chunk: Chunk) -> Result<(), SpawnError> {
488 crate::bytecode::verify(&chunk).map_err(|e| SpawnError::VerifyFailed(e.to_string()))?;
489 let chunk = Arc::new(chunk);
490 self.chunk = chunk.clone();
491 #[cfg(feature = "jit")]
492 if let Some(jit) = &self.shared.jit {
493 jit.reload(chunk);
494 }
495 Ok(())
496 }
497
498 pub fn shutdown(mut self) {
505 self.shared.shutdown.store(true, Ordering::Release);
506 self.shared.timer.shutdown();
507 {
508 let (lock, cvar) = &self.shared.notify;
509 match super::sync_lock::lock(lock, "Runtime::shutdown") {
510 Ok(_g) => cvar.notify_all(),
511 Err(e) => super::error::report_fault(e),
512 }
513 }
514 for w in self.workers.drain(..) {
515 let _ = w.join();
516 }
517 if let Some(t) = self.timer_thread.take() {
518 let _ = t.join();
519 }
520 }
521}
522
523pub fn flow_id_from_u64(raw: u64) -> FlowId {
527 FlowId(raw)
528}
529
530#[derive(Debug, Clone, PartialEq, Eq)]
532pub enum SendError {
533 NoSuchFlow(FlowId),
534 NotAHop { got: &'static str },
536 MailboxFull {
540 flow: FlowId,
541 reason: MailboxFullReason,
542 },
543}
544
545impl std::fmt::Display for SendError {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 match self {
548 SendError::NoSuchFlow(id) => write!(f, "no live flow {id}"),
549 SendError::NotAHop { got } => {
550 write!(f, "atomic hop requires Value::Message, got {got}")
551 }
552 SendError::MailboxFull { flow, reason } => {
553 write!(f, "mailbox full for {flow} ({reason})")
554 }
555 }
556 }
557}
558
559impl std::error::Error for SendError {}
560
561pub(crate) fn spawn_on(
570 shared: &Arc<Shared>,
571 chunk: &Arc<Chunk>,
572 natives: &Arc<NativeTable>,
573 function: u32,
574 args: &[Value],
575 restart_policy: RestartPolicy,
576 supervisor: Option<SupervisorLink>,
577) -> Result<FlowHandle, SpawnError> {
578 if shared.max_flows > 0 {
579 let current = shared.directory.len();
580 if current >= shared.max_flows as usize {
581 return Err(SpawnError::FlowLimit {
582 current,
583 max: shared.max_flows,
584 });
585 }
586 }
587 let id = super::process::next_flow_id();
588 let vm = Vm::new(chunk.clone(), natives.clone(), function, args)?;
589 let mailbox = Arc::new(Mailbox::with_config(shared.mailbox));
590 if let Err(e) = shared.directory.register(id, mailbox.clone()) {
591 super::error::report_fault(e);
592 return Err(SpawnError::VmInit(
593 "directory register failed (poisoned lock)".into(),
594 ));
595 }
596 let (tx, rx) = super::oneshot::channel();
597 let mut flow = Box::new(Flow::new(id, vm, mailbox, restart_policy, tx));
598 flow.supervisor = supervisor;
599 RuntimeMetrics::inc(&shared.metrics.processes_spawned);
600 shared.injector.push(flow);
601 wake_workers(shared);
602 Ok(FlowHandle { id, receiver: rx })
603}
604
605pub(crate) fn wake_workers(shared: &Shared) {
606 let (lock, cvar) = &shared.notify;
607 match super::sync_lock::lock(lock, "wake_workers") {
608 Ok(_g) => cvar.notify_one(),
609 Err(e) => super::error::report_fault(e),
610 }
611}
612
613#[derive(Clone)]
619pub struct RuntimeSpawner {
620 pub(crate) shared: Arc<Shared>,
621 pub(crate) chunk: Arc<Chunk>,
622 pub(crate) natives: Arc<NativeTable>,
623}
624
625impl RuntimeSpawner {
626 pub fn spawn(
627 &self,
628 function: u32,
629 args: &[Value],
630 restart_policy: RestartPolicy,
631 ) -> Result<FlowHandle, SpawnError> {
632 spawn_on(
633 &self.shared,
634 &self.chunk,
635 &self.natives,
636 function,
637 args,
638 restart_policy,
639 None,
640 )
641 }
642
643 pub(crate) fn spawn_linked(
644 &self,
645 function: u32,
646 args: &[Value],
647 restart_policy: RestartPolicy,
648 supervisor: SupervisorLink,
649 ) -> Result<FlowHandle, SpawnError> {
650 spawn_on(
651 &self.shared,
652 &self.chunk,
653 &self.natives,
654 function,
655 args,
656 restart_policy,
657 Some(supervisor),
658 )
659 }
660
661 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
662 self.shared.metrics.snapshot()
663 }
664
665 pub(crate) fn request_kill(&self, id: FlowId, reason: super::monitor::FlowExitReason) {
666 super::finalize::request_kill(&self.shared, id, reason);
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673 use crate::bytecode::{builder::ChunkBuilder, Opcode, Value};
674 use crate::scheduler::FlowOutcome;
675 use std::time::Duration;
676
677 fn add_chunk() -> Chunk {
678 let mut b = ChunkBuilder::new("test");
679 b.begin_function("main", 0, 2);
680 b.emit_load_imm(0, 41);
681 b.emit_load_imm(1, 1);
682 b.emit_binop(Opcode::Add, 0, 0, 1);
683 b.emit_return(0);
684 b.finish()
685 }
686
687 fn sleep_then_return_chunk(millis: i32) -> Chunk {
690 let mut b = ChunkBuilder::new("test");
691 b.begin_function("main", 0, 2);
692 b.emit_load_imm(0, millis);
693 b.emit_sleep(0);
694 b.emit_load_imm(0, 7);
695 b.emit_return(0);
696 b.finish()
697 }
698
699 #[test]
703 fn polling_and_bounded_waits_never_commit_the_host_thread() -> Result<(), Box<dyn std::error::Error>>
704 {
705 const FLOW_SLEEP: i32 = 150;
706 let rt = Runtime::with_config(
707 sleep_then_return_chunk(FLOW_SLEEP),
708 RuntimeConfig {
709 workers: 1,
710 quantum: 1_000,
711 mailbox: MailboxConfig::DEFAULT,
712 ..Default::default()
713 },
714 )?;
715 let handle = rt.spawn(0, &[])?;
716
717 if let Some(outcome) = handle.try_join() {
720 rt.shutdown();
721 return Err(format!("try_join answered too early: {outcome:?}").into());
722 }
723
724 if let Some(outcome) = handle.join_timeout(Duration::from_millis(20)) {
727 rt.shutdown();
728 return Err(format!("join_timeout answered too early: {outcome:?}").into());
729 }
730
731 let outcome = handle.join_timeout(Duration::from_secs(10));
734 rt.shutdown();
735 match outcome {
736 Some(FlowOutcome::Completed(Value::Int(7))) => Ok(()),
737 other => Err(format!("unexpected outcome: {other:?}").into()),
738 }
739 }
740
741 #[test]
742 fn spawn_and_join_add() -> Result<(), Box<dyn std::error::Error>> {
743 let rt = Runtime::with_config(
744 add_chunk(),
745 RuntimeConfig {
746 workers: 1,
747 quantum: 1_000,
748 mailbox: MailboxConfig::DEFAULT,
749 ..Default::default()
750 },
751 )?;
752 let outcome = rt.spawn(0, &[])?.join();
753 rt.shutdown();
754 match outcome {
755 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
756 other => Err(format!("unexpected outcome: {other:?}").into()),
757 }
758 }
759
760 fn receive_forever_chunk() -> Chunk {
761 let mut b = ChunkBuilder::new("recv");
762 b.begin_function("main", 0, 1);
763 b.emit_receive(0);
764 b.emit_return(0);
765 b.finish()
766 }
767
768 #[test]
769 fn kill_parked_flow_joins_failed() -> Result<(), Box<dyn std::error::Error>> {
770 let rt = Runtime::with_config(
771 receive_forever_chunk(),
772 RuntimeConfig {
773 workers: 1,
774 quantum: 1_000,
775 mailbox: MailboxConfig::DEFAULT,
776 ..Default::default()
777 },
778 )?;
779 let handle = rt.spawn(0, &[])?;
780 rt.kill(handle.id())?;
781 let outcome = handle.join();
782 rt.shutdown();
783 assert!(
784 matches!(outcome, FlowOutcome::Failed(_)),
785 "kill must fail the joiner, got {outcome:?}"
786 );
787 Ok(())
788 }
789
790 #[test]
791 fn max_flows_rejects_extra_spawn() -> Result<(), Box<dyn std::error::Error>> {
792 let rt = Runtime::with_config(
793 receive_forever_chunk(),
794 RuntimeConfig {
795 workers: 1,
796 quantum: 1_000,
797 mailbox: MailboxConfig::DEFAULT,
798 max_flows: 1,
799 ..Default::default()
800 },
801 )?;
802 let first = rt.spawn(0, &[])?;
803 let second = rt.spawn(0, &[]);
804 rt.kill(first.id())?;
805 let _ = first.join();
806 rt.shutdown();
807 match second {
808 Err(SpawnError::FlowLimit { current, max }) => {
809 assert_eq!(current, 1);
810 assert_eq!(max, 1);
811 Ok(())
812 }
813 other => Err(format!(
814 "expected FlowLimit, got {}",
815 match &other {
816 Ok(_) => "Ok(handle)".into(),
817 Err(e) => format!("Err({e})"),
818 }
819 )
820 .into()),
821 }
822 }
823
824 #[cfg(feature = "jit")]
825 #[test]
826 fn runtime_with_jit_enabled_completes_add() -> Result<(), Box<dyn std::error::Error>> {
827 use crate::JitConfig;
828
829 let rt = Runtime::with_config(
830 add_chunk(),
831 RuntimeConfig {
832 workers: 1,
833 quantum: 1_000,
834 mailbox: MailboxConfig::DEFAULT,
835 jit: JitConfig {
836 enabled: true,
837 hot_threshold: 1,
838 },
839 ..Default::default()
840 },
841 )?;
842 let outcome = rt.spawn(0, &[])?.join();
843 rt.shutdown();
844 match outcome {
845 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
846 other => Err(format!("unexpected outcome: {other:?}").into()),
847 }
848 }
849}