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}
45
46impl Default for RuntimeConfig {
47 fn default() -> Self {
48 RuntimeConfig {
49 workers: num_cpus::get().max(1),
50 quantum: DEFAULT_QUANTUM,
51 mailbox: MailboxConfig::DEFAULT,
52 }
53 }
54}
55
56pub struct Shared {
63 pub(crate) injector: Injector<Box<Flow>>,
64 pub(crate) stealers: Vec<Stealer<Box<Flow>>>,
65 pub(crate) directory: Directory,
67 pub(crate) caps: super::capability::CapTable,
69 pub(crate) timer: Arc<TimerWheel>,
70 pub(crate) notify: (Mutex<()>, Condvar),
71 pub(crate) metrics: RuntimeMetrics,
72 pub(crate) shutdown: AtomicBool,
73 pub(crate) quantum: u32,
74 pub(crate) mailbox: MailboxConfig,
75}
76
77pub struct Runtime {
88 shared: Arc<Shared>,
89 chunk: Arc<Chunk>,
90 natives: Arc<NativeTable>,
91 workers: Vec<JoinHandle<()>>,
92 timer_thread: Option<JoinHandle<()>>,
93}
94
95impl Runtime {
96 pub fn new(chunk: Chunk) -> Result<Self, SpawnError> {
104 Self::with_config(chunk, RuntimeConfig::default())
105 }
106
107 pub fn with_natives(chunk: Chunk, natives: Arc<NativeTable>) -> Result<Self, SpawnError> {
110 Self::with_natives_and_config(chunk, natives, RuntimeConfig::default())
111 }
112
113 pub fn with_config(chunk: Chunk, config: RuntimeConfig) -> Result<Self, SpawnError> {
114 Self::with_natives_and_config(chunk, NativeTable::empty(), config)
115 }
116
117 pub fn with_natives_and_config(
125 chunk: Chunk,
126 natives: Arc<NativeTable>,
127 config: RuntimeConfig,
128 ) -> Result<Self, SpawnError> {
129 crate::bytecode::verify(&chunk).map_err(|e| SpawnError::VerifyFailed(e.to_string()))?;
130 let chunk = Arc::new(chunk);
131 let workers_n = config.workers.max(1);
132
133 let locals: Vec<LocalDeque<Box<Flow>>> =
134 (0..workers_n).map(|_| LocalDeque::new_fifo()).collect();
135 let stealers: Vec<Stealer<Box<Flow>>> = locals.iter().map(|l| l.stealer()).collect();
136
137 let shared = Arc::new(Shared {
138 injector: Injector::new(),
139 stealers,
140 directory: Directory::new(),
141 caps: super::capability::CapTable::new(),
142 timer: TimerWheel::new(),
143 notify: (Mutex::new(()), Condvar::new()),
144 metrics: RuntimeMetrics::default(),
145 shutdown: AtomicBool::new(false),
146 quantum: config.quantum,
147 mailbox: config.mailbox,
148 });
149
150 let mut workers = Vec::with_capacity(workers_n);
151 for local in locals {
152 let shared = shared.clone();
153 let handle = std::thread::Builder::new()
154 .name("byteflow-worker".into())
155 .spawn(move || worker::run_worker(shared, local))
156 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
157 workers.push(handle);
158 }
159
160 let shared_timer = shared.clone();
161 let timer_thread = std::thread::Builder::new()
162 .name("byteflow-timer".into())
163 .spawn(move || {
164 shared_timer
165 .timer
166 .clone()
167 .drive(&shared_timer.injector, &shared_timer.notify)
168 })
169 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
170
171 Ok(Runtime {
172 shared,
173 chunk,
174 natives,
175 workers,
176 timer_thread: Some(timer_thread),
177 })
178 }
179
180 pub fn spawn(&self, function: u32, args: &[Value]) -> Result<FlowHandle, SpawnError> {
185 spawn_on(
186 &self.shared,
187 &self.chunk,
188 &self.natives,
189 function,
190 args,
191 RestartPolicy::Never,
192 None,
193 )
194 }
195
196 pub fn spawner(&self) -> RuntimeSpawner {
200 RuntimeSpawner { shared: self.shared.clone(), chunk: self.chunk.clone(), natives: self.natives.clone() }
201 }
202
203 pub fn supervisor(&self) -> Result<super::supervisor::Supervisor, SpawnError> {
206 super::supervisor::Supervisor::new(self.spawner())
207 }
208
209 pub fn function_index(&self, name: &str) -> Option<u32> {
213 self.chunk.functions.iter().position(|f| f.name == name).map(|i| i as u32)
214 }
215
216 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
217 self.shared.metrics.snapshot()
218 }
219
220 pub fn live_flows(&self) -> usize {
224 self.shared.directory.len()
225 }
226
227 pub fn worker_count(&self) -> usize {
228 self.workers.len()
229 }
230
231 pub fn send(&self, target: FlowId, message: Value) -> Result<(), SendError> {
242 if message.as_message().is_none() {
243 return Err(SendError::NotAHop {
244 got: message.type_name(),
245 });
246 }
247 let mailbox = match self.shared.directory.lookup(target) {
248 Ok(Some(m)) => m,
249 Ok(None) => return Err(SendError::NoSuchFlow(target)),
250 Err(e) => {
251 super::error::report_fault(e);
252 return Err(SendError::NoSuchFlow(target));
253 }
254 };
255 match mailbox.push(message.clone()) {
256 Ok(Ok(Delivery::Queued | Delivery::QueuedDropOldest | Delivery::DroppedNewest)) => {
257 Ok(())
258 }
259 Ok(Ok(Delivery::Handoff(mut flow))) => {
260 if let Some(dest) = flow.last_receive_dest {
261 let _ = flow.vm.resume_with(dest, message);
262 }
263 self.shared.injector.push(flow);
264 wake_workers(&self.shared);
265 Ok(())
266 }
267 Ok(Err(full)) => Err(SendError::MailboxFull {
268 flow: target,
269 reason: full.reason(),
270 }),
271 Err(e) => {
272 super::error::report_fault(e);
273 Err(SendError::NoSuchFlow(target))
274 }
275 }
276 }
277
278 pub fn shutdown(mut self) {
285 self.shared.shutdown.store(true, Ordering::Release);
286 self.shared.timer.shutdown();
287 {
288 let (lock, cvar) = &self.shared.notify;
289 match super::sync_lock::lock(lock, "Runtime::shutdown") {
290 Ok(_g) => cvar.notify_all(),
291 Err(e) => super::error::report_fault(e),
292 }
293 }
294 for w in self.workers.drain(..) {
295 let _ = w.join();
296 }
297 if let Some(t) = self.timer_thread.take() {
298 let _ = t.join();
299 }
300 }
301}
302
303pub fn flow_id_from_u64(raw: u64) -> FlowId {
307 FlowId(raw)
308}
309
310#[derive(Debug, Clone, PartialEq, Eq)]
312pub enum SendError {
313 NoSuchFlow(FlowId),
314 NotAHop { got: &'static str },
316 MailboxFull {
320 flow: FlowId,
321 reason: MailboxFullReason,
322 },
323}
324
325impl std::fmt::Display for SendError {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 match self {
328 SendError::NoSuchFlow(id) => write!(f, "no live flow {id}"),
329 SendError::NotAHop { got } => {
330 write!(f, "atomic hop requires Value::Message, got {got}")
331 }
332 SendError::MailboxFull { flow, reason } => {
333 write!(f, "mailbox full for {flow} ({reason})")
334 }
335 }
336 }
337}
338
339impl std::error::Error for SendError {}
340
341pub(crate) fn spawn_on(
350 shared: &Arc<Shared>,
351 chunk: &Arc<Chunk>,
352 natives: &Arc<NativeTable>,
353 function: u32,
354 args: &[Value],
355 restart_policy: RestartPolicy,
356 supervisor: Option<SupervisorLink>,
357) -> Result<FlowHandle, SpawnError> {
358 let id = super::process::next_flow_id();
359 let vm = Vm::new(chunk.clone(), natives.clone(), function, args)?;
360 let mailbox = Arc::new(Mailbox::with_config(shared.mailbox));
361 if let Err(e) = shared.directory.register(id, mailbox.clone()) {
362 super::error::report_fault(e);
363 return Err(SpawnError::VmInit(
364 "directory register failed (poisoned lock)".into(),
365 ));
366 }
367 let (tx, rx) = super::oneshot::channel();
368 let mut flow = Box::new(Flow::new(id, vm, mailbox, restart_policy, tx));
369 flow.supervisor = supervisor;
370 RuntimeMetrics::inc(&shared.metrics.processes_spawned);
371 shared.injector.push(flow);
372 wake_workers(shared);
373 Ok(FlowHandle { id, receiver: rx })
374}
375
376pub(crate) fn wake_workers(shared: &Shared) {
377 let (lock, cvar) = &shared.notify;
378 match super::sync_lock::lock(lock, "wake_workers") {
379 Ok(_g) => cvar.notify_one(),
380 Err(e) => super::error::report_fault(e),
381 }
382}
383
384#[derive(Clone)]
390pub struct RuntimeSpawner {
391 pub(crate) shared: Arc<Shared>,
392 pub(crate) chunk: Arc<Chunk>,
393 pub(crate) natives: Arc<NativeTable>,
394}
395
396impl RuntimeSpawner {
397 pub fn spawn(
398 &self,
399 function: u32,
400 args: &[Value],
401 restart_policy: RestartPolicy,
402 ) -> Result<FlowHandle, SpawnError> {
403 spawn_on(
404 &self.shared,
405 &self.chunk,
406 &self.natives,
407 function,
408 args,
409 restart_policy,
410 None,
411 )
412 }
413
414 pub(crate) fn spawn_linked(
415 &self,
416 function: u32,
417 args: &[Value],
418 restart_policy: RestartPolicy,
419 supervisor: SupervisorLink,
420 ) -> Result<FlowHandle, SpawnError> {
421 spawn_on(
422 &self.shared,
423 &self.chunk,
424 &self.natives,
425 function,
426 args,
427 restart_policy,
428 Some(supervisor),
429 )
430 }
431
432 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
433 self.shared.metrics.snapshot()
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440 use crate::bytecode::{ChunkBuilder, Opcode, Value};
441 use crate::scheduler::FlowOutcome;
442 use std::time::Duration;
443
444 fn add_chunk() -> Chunk {
445 let mut b = ChunkBuilder::new("test");
446 b.begin_function("main", 0, 2);
447 b.emit_load_imm(0, 41);
448 b.emit_load_imm(1, 1);
449 b.emit_binop(Opcode::Add, 0, 0, 1);
450 b.emit_return(0);
451 b.finish()
452 }
453
454 fn sleep_then_return_chunk(millis: i32) -> Chunk {
457 let mut b = ChunkBuilder::new("test");
458 b.begin_function("main", 0, 2);
459 b.emit_load_imm(0, millis);
460 b.emit_sleep(0);
461 b.emit_load_imm(0, 7);
462 b.emit_return(0);
463 b.finish()
464 }
465
466 #[test]
470 fn polling_and_bounded_waits_never_commit_the_host_thread() -> Result<(), Box<dyn std::error::Error>>
471 {
472 const FLOW_SLEEP: i32 = 150;
473 let rt = Runtime::with_config(
474 sleep_then_return_chunk(FLOW_SLEEP),
475 RuntimeConfig {
476 workers: 1,
477 quantum: 1_000,
478 mailbox: MailboxConfig::DEFAULT,
479 },
480 )?;
481 let handle = rt.spawn(0, &[])?;
482
483 if let Some(outcome) = handle.try_join() {
486 rt.shutdown();
487 return Err(format!("try_join answered too early: {outcome:?}").into());
488 }
489
490 if let Some(outcome) = handle.join_timeout(Duration::from_millis(20)) {
493 rt.shutdown();
494 return Err(format!("join_timeout answered too early: {outcome:?}").into());
495 }
496
497 let outcome = handle.join_timeout(Duration::from_secs(10));
500 rt.shutdown();
501 match outcome {
502 Some(FlowOutcome::Completed(Value::Int(7))) => Ok(()),
503 other => Err(format!("unexpected outcome: {other:?}").into()),
504 }
505 }
506
507 #[test]
508 fn spawn_and_join_add() -> Result<(), Box<dyn std::error::Error>> {
509 let rt = Runtime::with_config(
510 add_chunk(),
511 RuntimeConfig {
512 workers: 1,
513 quantum: 1_000,
514 mailbox: MailboxConfig::DEFAULT,
515 },
516 )?;
517 let outcome = rt.spawn(0, &[])?.join();
518 rt.shutdown();
519 match outcome {
520 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
521 other => Err(format!("unexpected outcome: {other:?}").into()),
522 }
523 }
524}