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 #[cfg(feature = "jit")]
46 pub jit: JitConfig,
47}
48
49#[cfg(feature = "jit")]
51#[derive(Clone, Debug)]
52pub struct JitConfig {
53 pub enabled: bool,
55 pub hot_threshold: u32,
57}
58
59#[cfg(feature = "jit")]
60impl Default for JitConfig {
61 fn default() -> Self {
62 JitConfig {
63 enabled: false,
64 hot_threshold: crate::jit::HOT_THRESHOLD,
65 }
66 }
67}
68
69impl Default for RuntimeConfig {
70 fn default() -> Self {
71 RuntimeConfig {
72 workers: num_cpus::get().max(1),
73 quantum: DEFAULT_QUANTUM,
74 mailbox: MailboxConfig::DEFAULT,
75 #[cfg(feature = "jit")]
76 jit: JitConfig::default(),
77 }
78 }
79}
80
81pub struct Shared {
88 pub(crate) injector: Injector<Box<Flow>>,
89 pub(crate) stealers: Vec<Stealer<Box<Flow>>>,
90 pub(crate) directory: Directory,
92 pub(crate) caps: super::capability::CapTable,
94 pub(crate) timer: Arc<TimerWheel>,
95 pub(crate) notify: (Mutex<()>, Condvar),
96 pub(crate) metrics: RuntimeMetrics,
97 pub(crate) shutdown: AtomicBool,
98 pub(crate) quantum: u32,
99 pub(crate) mailbox: MailboxConfig,
100 #[cfg(feature = "jit")]
102 pub(crate) jit: Option<std::sync::Arc<crate::jit::JitRuntime>>,
103}
104
105pub struct Runtime {
116 shared: Arc<Shared>,
117 chunk: Arc<Chunk>,
118 natives: Arc<NativeTable>,
119 workers: Vec<JoinHandle<()>>,
120 timer_thread: Option<JoinHandle<()>>,
121}
122
123impl Runtime {
124 pub fn new(chunk: Chunk) -> Result<Self, SpawnError> {
132 Self::with_config(chunk, RuntimeConfig::default())
133 }
134
135 pub fn with_natives(chunk: Chunk, natives: Arc<NativeTable>) -> Result<Self, SpawnError> {
138 Self::with_natives_and_config(chunk, natives, RuntimeConfig::default())
139 }
140
141 pub fn with_config(chunk: Chunk, config: RuntimeConfig) -> Result<Self, SpawnError> {
142 Self::with_natives_and_config(chunk, NativeTable::empty(), config)
143 }
144
145 pub fn with_natives_and_config(
153 chunk: Chunk,
154 natives: Arc<NativeTable>,
155 config: RuntimeConfig,
156 ) -> Result<Self, SpawnError> {
157 crate::bytecode::verify(&chunk).map_err(|e| SpawnError::VerifyFailed(e.to_string()))?;
158 let chunk = Arc::new(chunk);
159 let workers_n = config.workers.max(1);
160
161 let locals: Vec<LocalDeque<Box<Flow>>> =
162 (0..workers_n).map(|_| LocalDeque::new_fifo()).collect();
163 let stealers: Vec<Stealer<Box<Flow>>> = locals.iter().map(|l| l.stealer()).collect();
164
165 #[cfg(feature = "jit")]
166 let jit = if config.jit.enabled {
167 Some(super::jit::new_runtime(chunk.clone(), config.jit.hot_threshold))
168 } else {
169 None
170 };
171
172 let shared = Arc::new(Shared {
173 injector: Injector::new(),
174 stealers,
175 directory: Directory::new(),
176 caps: super::capability::CapTable::new(),
177 timer: TimerWheel::new(),
178 notify: (Mutex::new(()), Condvar::new()),
179 metrics: RuntimeMetrics::default(),
180 shutdown: AtomicBool::new(false),
181 quantum: config.quantum,
182 mailbox: config.mailbox,
183 #[cfg(feature = "jit")]
184 jit,
185 });
186
187 let mut workers = Vec::with_capacity(workers_n);
188 for local in locals {
189 let shared = shared.clone();
190 let handle = std::thread::Builder::new()
191 .name("byteflow-worker".into())
192 .spawn(move || worker::run_worker(shared, local))
193 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
194 workers.push(handle);
195 }
196
197 let shared_timer = shared.clone();
198 let timer_thread = std::thread::Builder::new()
199 .name("byteflow-timer".into())
200 .spawn(move || {
201 shared_timer
202 .timer
203 .clone()
204 .drive(&shared_timer.injector, &shared_timer.notify)
205 })
206 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
207
208 Ok(Runtime {
209 shared,
210 chunk,
211 natives,
212 workers,
213 timer_thread: Some(timer_thread),
214 })
215 }
216
217 pub fn spawn(&self, function: u32, args: &[Value]) -> Result<FlowHandle, SpawnError> {
222 spawn_on(
223 &self.shared,
224 &self.chunk,
225 &self.natives,
226 function,
227 args,
228 RestartPolicy::Never,
229 None,
230 )
231 }
232
233 pub fn spawner(&self) -> RuntimeSpawner {
237 RuntimeSpawner { shared: self.shared.clone(), chunk: self.chunk.clone(), natives: self.natives.clone() }
238 }
239
240 pub fn supervisor(&self) -> Result<super::supervisor::Supervisor, SpawnError> {
243 super::supervisor::Supervisor::new(self.spawner())
244 }
245
246 pub fn function_index(&self, name: &str) -> Option<u32> {
250 self.chunk.functions.iter().position(|f| f.name == name).map(|i| i as u32)
251 }
252
253 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
254 self.shared.metrics.snapshot()
255 }
256
257 pub fn live_flows(&self) -> usize {
261 self.shared.directory.len()
262 }
263
264 pub fn worker_count(&self) -> usize {
265 self.workers.len()
266 }
267
268 pub fn send(&self, target: FlowId, message: Value) -> Result<(), SendError> {
279 if message.as_message().is_none() {
280 return Err(SendError::NotAHop {
281 got: message.type_name(),
282 });
283 }
284 let mailbox = match self.shared.directory.lookup(target) {
285 Ok(Some(m)) => m,
286 Ok(None) => return Err(SendError::NoSuchFlow(target)),
287 Err(e) => {
288 super::error::report_fault(e);
289 return Err(SendError::NoSuchFlow(target));
290 }
291 };
292 match mailbox.push(message.clone()) {
293 Ok(Ok(Delivery::Queued | Delivery::QueuedDropOldest | Delivery::DroppedNewest)) => {
294 Ok(())
295 }
296 Ok(Ok(Delivery::Handoff(mut flow))) => {
297 if let Some(dest) = flow.last_receive_dest {
298 let _ = flow.vm.resume_with(dest, message);
299 }
300 self.shared.injector.push(flow);
301 wake_workers(&self.shared);
302 Ok(())
303 }
304 Ok(Err(full)) => Err(SendError::MailboxFull {
305 flow: target,
306 reason: full.reason(),
307 }),
308 Err(e) => {
309 super::error::report_fault(e);
310 Err(SendError::NoSuchFlow(target))
311 }
312 }
313 }
314
315 pub fn reload_chunk(&mut self, chunk: Chunk) -> Result<(), SpawnError> {
317 crate::bytecode::verify(&chunk).map_err(|e| SpawnError::VerifyFailed(e.to_string()))?;
318 let chunk = Arc::new(chunk);
319 self.chunk = chunk.clone();
320 #[cfg(feature = "jit")]
321 if let Some(jit) = &self.shared.jit {
322 jit.reload(chunk);
323 }
324 Ok(())
325 }
326
327 pub fn shutdown(mut self) {
334 self.shared.shutdown.store(true, Ordering::Release);
335 self.shared.timer.shutdown();
336 {
337 let (lock, cvar) = &self.shared.notify;
338 match super::sync_lock::lock(lock, "Runtime::shutdown") {
339 Ok(_g) => cvar.notify_all(),
340 Err(e) => super::error::report_fault(e),
341 }
342 }
343 for w in self.workers.drain(..) {
344 let _ = w.join();
345 }
346 if let Some(t) = self.timer_thread.take() {
347 let _ = t.join();
348 }
349 }
350}
351
352pub fn flow_id_from_u64(raw: u64) -> FlowId {
356 FlowId(raw)
357}
358
359#[derive(Debug, Clone, PartialEq, Eq)]
361pub enum SendError {
362 NoSuchFlow(FlowId),
363 NotAHop { got: &'static str },
365 MailboxFull {
369 flow: FlowId,
370 reason: MailboxFullReason,
371 },
372}
373
374impl std::fmt::Display for SendError {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 match self {
377 SendError::NoSuchFlow(id) => write!(f, "no live flow {id}"),
378 SendError::NotAHop { got } => {
379 write!(f, "atomic hop requires Value::Message, got {got}")
380 }
381 SendError::MailboxFull { flow, reason } => {
382 write!(f, "mailbox full for {flow} ({reason})")
383 }
384 }
385 }
386}
387
388impl std::error::Error for SendError {}
389
390pub(crate) fn spawn_on(
399 shared: &Arc<Shared>,
400 chunk: &Arc<Chunk>,
401 natives: &Arc<NativeTable>,
402 function: u32,
403 args: &[Value],
404 restart_policy: RestartPolicy,
405 supervisor: Option<SupervisorLink>,
406) -> Result<FlowHandle, SpawnError> {
407 let id = super::process::next_flow_id();
408 let vm = Vm::new(chunk.clone(), natives.clone(), function, args)?;
409 let mailbox = Arc::new(Mailbox::with_config(shared.mailbox));
410 if let Err(e) = shared.directory.register(id, mailbox.clone()) {
411 super::error::report_fault(e);
412 return Err(SpawnError::VmInit(
413 "directory register failed (poisoned lock)".into(),
414 ));
415 }
416 let (tx, rx) = super::oneshot::channel();
417 let mut flow = Box::new(Flow::new(id, vm, mailbox, restart_policy, tx));
418 flow.supervisor = supervisor;
419 RuntimeMetrics::inc(&shared.metrics.processes_spawned);
420 shared.injector.push(flow);
421 wake_workers(shared);
422 Ok(FlowHandle { id, receiver: rx })
423}
424
425pub(crate) fn wake_workers(shared: &Shared) {
426 let (lock, cvar) = &shared.notify;
427 match super::sync_lock::lock(lock, "wake_workers") {
428 Ok(_g) => cvar.notify_one(),
429 Err(e) => super::error::report_fault(e),
430 }
431}
432
433#[derive(Clone)]
439pub struct RuntimeSpawner {
440 pub(crate) shared: Arc<Shared>,
441 pub(crate) chunk: Arc<Chunk>,
442 pub(crate) natives: Arc<NativeTable>,
443}
444
445impl RuntimeSpawner {
446 pub fn spawn(
447 &self,
448 function: u32,
449 args: &[Value],
450 restart_policy: RestartPolicy,
451 ) -> Result<FlowHandle, SpawnError> {
452 spawn_on(
453 &self.shared,
454 &self.chunk,
455 &self.natives,
456 function,
457 args,
458 restart_policy,
459 None,
460 )
461 }
462
463 pub(crate) fn spawn_linked(
464 &self,
465 function: u32,
466 args: &[Value],
467 restart_policy: RestartPolicy,
468 supervisor: SupervisorLink,
469 ) -> Result<FlowHandle, SpawnError> {
470 spawn_on(
471 &self.shared,
472 &self.chunk,
473 &self.natives,
474 function,
475 args,
476 restart_policy,
477 Some(supervisor),
478 )
479 }
480
481 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
482 self.shared.metrics.snapshot()
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::bytecode::{builder::ChunkBuilder, Opcode, Value};
490 use crate::scheduler::FlowOutcome;
491 use std::time::Duration;
492
493 fn add_chunk() -> Chunk {
494 let mut b = ChunkBuilder::new("test");
495 b.begin_function("main", 0, 2);
496 b.emit_load_imm(0, 41);
497 b.emit_load_imm(1, 1);
498 b.emit_binop(Opcode::Add, 0, 0, 1);
499 b.emit_return(0);
500 b.finish()
501 }
502
503 fn sleep_then_return_chunk(millis: i32) -> Chunk {
506 let mut b = ChunkBuilder::new("test");
507 b.begin_function("main", 0, 2);
508 b.emit_load_imm(0, millis);
509 b.emit_sleep(0);
510 b.emit_load_imm(0, 7);
511 b.emit_return(0);
512 b.finish()
513 }
514
515 #[test]
519 fn polling_and_bounded_waits_never_commit_the_host_thread() -> Result<(), Box<dyn std::error::Error>>
520 {
521 const FLOW_SLEEP: i32 = 150;
522 let rt = Runtime::with_config(
523 sleep_then_return_chunk(FLOW_SLEEP),
524 RuntimeConfig {
525 workers: 1,
526 quantum: 1_000,
527 mailbox: MailboxConfig::DEFAULT,
528 ..Default::default()
529 },
530 )?;
531 let handle = rt.spawn(0, &[])?;
532
533 if let Some(outcome) = handle.try_join() {
536 rt.shutdown();
537 return Err(format!("try_join answered too early: {outcome:?}").into());
538 }
539
540 if let Some(outcome) = handle.join_timeout(Duration::from_millis(20)) {
543 rt.shutdown();
544 return Err(format!("join_timeout answered too early: {outcome:?}").into());
545 }
546
547 let outcome = handle.join_timeout(Duration::from_secs(10));
550 rt.shutdown();
551 match outcome {
552 Some(FlowOutcome::Completed(Value::Int(7))) => Ok(()),
553 other => Err(format!("unexpected outcome: {other:?}").into()),
554 }
555 }
556
557 #[test]
558 fn spawn_and_join_add() -> Result<(), Box<dyn std::error::Error>> {
559 let rt = Runtime::with_config(
560 add_chunk(),
561 RuntimeConfig {
562 workers: 1,
563 quantum: 1_000,
564 mailbox: MailboxConfig::DEFAULT,
565 ..Default::default()
566 },
567 )?;
568 let outcome = rt.spawn(0, &[])?.join();
569 rt.shutdown();
570 match outcome {
571 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
572 other => Err(format!("unexpected outcome: {other:?}").into()),
573 }
574 }
575
576 #[cfg(feature = "jit")]
577 #[test]
578 fn runtime_with_jit_enabled_completes_add() -> Result<(), Box<dyn std::error::Error>> {
579 use crate::JitConfig;
580
581 let rt = Runtime::with_config(
582 add_chunk(),
583 RuntimeConfig {
584 workers: 1,
585 quantum: 1_000,
586 mailbox: MailboxConfig::DEFAULT,
587 jit: JitConfig {
588 enabled: true,
589 hot_threshold: 1,
590 },
591 ..Default::default()
592 },
593 )?;
594 let outcome = rt.spawn(0, &[])?.join();
595 rt.shutdown();
596 match outcome {
597 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
598 other => Err(format!("unexpected outcome: {other:?}").into()),
599 }
600 }
601}