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};
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}
41
42impl Default for RuntimeConfig {
43 fn default() -> Self {
44 RuntimeConfig { workers: num_cpus::get().max(1), quantum: DEFAULT_QUANTUM }
45 }
46}
47
48pub struct Shared {
55 pub(crate) injector: Injector<Box<Flow>>,
56 pub(crate) stealers: Vec<Stealer<Box<Flow>>>,
57 pub(crate) directory: Directory,
59 pub(crate) caps: super::capability::CapTable,
61 pub(crate) timer: Arc<TimerWheel>,
62 pub(crate) notify: (Mutex<()>, Condvar),
63 pub(crate) metrics: RuntimeMetrics,
64 pub(crate) shutdown: AtomicBool,
65 pub(crate) quantum: u32,
66}
67
68pub struct Runtime {
74 shared: Arc<Shared>,
75 chunk: Arc<Chunk>,
76 natives: Arc<NativeTable>,
77 workers: Vec<JoinHandle<()>>,
78 timer_thread: Option<JoinHandle<()>>,
79}
80
81impl Runtime {
82 pub fn new(chunk: Chunk) -> Result<Self, SpawnError> {
90 Self::with_config(chunk, RuntimeConfig::default())
91 }
92
93 pub fn with_natives(chunk: Chunk, natives: Arc<NativeTable>) -> Result<Self, SpawnError> {
96 Self::with_natives_and_config(chunk, natives, RuntimeConfig::default())
97 }
98
99 pub fn with_config(chunk: Chunk, config: RuntimeConfig) -> Result<Self, SpawnError> {
100 Self::with_natives_and_config(chunk, NativeTable::empty(), config)
101 }
102
103 pub fn with_natives_and_config(
111 chunk: Chunk,
112 natives: Arc<NativeTable>,
113 config: RuntimeConfig,
114 ) -> Result<Self, SpawnError> {
115 crate::bytecode::verify(&chunk).map_err(|e| SpawnError::VerifyFailed(e.to_string()))?;
116 let chunk = Arc::new(chunk);
117 let workers_n = config.workers.max(1);
118
119 let locals: Vec<LocalDeque<Box<Flow>>> =
120 (0..workers_n).map(|_| LocalDeque::new_fifo()).collect();
121 let stealers: Vec<Stealer<Box<Flow>>> = locals.iter().map(|l| l.stealer()).collect();
122
123 let shared = Arc::new(Shared {
124 injector: Injector::new(),
125 stealers,
126 directory: Directory::new(),
127 caps: super::capability::CapTable::new(),
128 timer: TimerWheel::new(),
129 notify: (Mutex::new(()), Condvar::new()),
130 metrics: RuntimeMetrics::default(),
131 shutdown: AtomicBool::new(false),
132 quantum: config.quantum,
133 });
134
135 let mut workers = Vec::with_capacity(workers_n);
136 for local in locals {
137 let shared = shared.clone();
138 let handle = std::thread::Builder::new()
139 .name("byteflow-worker".into())
140 .spawn(move || worker::run_worker(shared, local))
141 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
142 workers.push(handle);
143 }
144
145 let shared_timer = shared.clone();
146 let timer_thread = std::thread::Builder::new()
147 .name("byteflow-timer".into())
148 .spawn(move || {
149 shared_timer
150 .timer
151 .clone()
152 .drive(&shared_timer.injector, &shared_timer.notify)
153 })
154 .map_err(|e| SpawnError::ThreadSpawnFailed(e.to_string()))?;
155
156 Ok(Runtime {
157 shared,
158 chunk,
159 natives,
160 workers,
161 timer_thread: Some(timer_thread),
162 })
163 }
164
165 pub fn spawn(&self, function: u32, args: &[Value]) -> Result<FlowHandle, SpawnError> {
170 spawn_on(
171 &self.shared,
172 &self.chunk,
173 &self.natives,
174 function,
175 args,
176 RestartPolicy::Never,
177 None,
178 )
179 }
180
181 pub fn spawner(&self) -> RuntimeSpawner {
185 RuntimeSpawner { shared: self.shared.clone(), chunk: self.chunk.clone(), natives: self.natives.clone() }
186 }
187
188 pub fn supervisor(&self) -> Result<super::supervisor::Supervisor, SpawnError> {
191 super::supervisor::Supervisor::new(self.spawner())
192 }
193
194 pub fn function_index(&self, name: &str) -> Option<u32> {
198 self.chunk.functions.iter().position(|f| f.name == name).map(|i| i as u32)
199 }
200
201 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
202 self.shared.metrics.snapshot()
203 }
204
205 pub fn live_flows(&self) -> usize {
209 self.shared.directory.len()
210 }
211
212 pub fn worker_count(&self) -> usize {
213 self.workers.len()
214 }
215
216 pub fn send(&self, target: FlowId, message: Value) -> Result<(), SendError> {
227 if message.as_message().is_none() {
228 return Err(SendError::NotAHop {
229 got: message.type_name(),
230 });
231 }
232 let mailbox = match self.shared.directory.lookup(target) {
233 Ok(Some(m)) => m,
234 Ok(None) => return Err(SendError::NoSuchFlow(target)),
235 Err(e) => {
236 super::error::report_fault(e);
237 return Err(SendError::NoSuchFlow(target));
238 }
239 };
240 match mailbox.push(message.clone()) {
241 Ok(Delivery::Queued) => Ok(()),
242 Ok(Delivery::Handoff(mut flow)) => {
243 if let Some(dest) = flow.last_receive_dest {
244 let _ = flow.vm.resume_with(dest, message);
245 }
246 self.shared.injector.push(flow);
247 wake_workers(&self.shared);
248 Ok(())
249 }
250 Err(e) => {
251 super::error::report_fault(e);
252 Err(SendError::NoSuchFlow(target))
253 }
254 }
255 }
256
257 pub fn shutdown(mut self) {
264 self.shared.shutdown.store(true, Ordering::Release);
265 self.shared.timer.shutdown();
266 {
267 let (lock, cvar) = &self.shared.notify;
268 match super::sync_lock::lock(lock, "Runtime::shutdown") {
269 Ok(_g) => cvar.notify_all(),
270 Err(e) => super::error::report_fault(e),
271 }
272 }
273 for w in self.workers.drain(..) {
274 let _ = w.join();
275 }
276 if let Some(t) = self.timer_thread.take() {
277 let _ = t.join();
278 }
279 }
280}
281
282pub fn flow_id_from_u64(raw: u64) -> FlowId {
286 FlowId(raw)
287}
288
289#[derive(Debug, Clone, PartialEq, Eq)]
291pub enum SendError {
292 NoSuchFlow(FlowId),
293 NotAHop { got: &'static str },
295}
296
297impl std::fmt::Display for SendError {
298 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299 match self {
300 SendError::NoSuchFlow(id) => write!(f, "no live flow {id}"),
301 SendError::NotAHop { got } => {
302 write!(f, "atomic hop requires Value::Message, got {got}")
303 }
304 }
305 }
306}
307
308impl std::error::Error for SendError {}
309
310pub(crate) fn spawn_on(
319 shared: &Arc<Shared>,
320 chunk: &Arc<Chunk>,
321 natives: &Arc<NativeTable>,
322 function: u32,
323 args: &[Value],
324 restart_policy: RestartPolicy,
325 supervisor: Option<SupervisorLink>,
326) -> Result<FlowHandle, SpawnError> {
327 let id = super::process::next_flow_id();
328 let vm = Vm::new(chunk.clone(), natives.clone(), function, args)?;
329 let mailbox = Arc::new(Mailbox::new());
330 if let Err(e) = shared.directory.register(id, mailbox.clone()) {
331 super::error::report_fault(e);
332 return Err(SpawnError::VmInit(
333 "directory register failed (poisoned lock)".into(),
334 ));
335 }
336 let (tx, rx) = super::oneshot::channel();
337 let mut flow = Box::new(Flow::new(id, vm, mailbox, restart_policy, tx));
338 flow.supervisor = supervisor;
339 RuntimeMetrics::inc(&shared.metrics.processes_spawned);
340 shared.injector.push(flow);
341 wake_workers(shared);
342 Ok(FlowHandle { id, receiver: rx })
343}
344
345pub(crate) fn wake_workers(shared: &Shared) {
346 let (lock, cvar) = &shared.notify;
347 match super::sync_lock::lock(lock, "wake_workers") {
348 Ok(_g) => cvar.notify_one(),
349 Err(e) => super::error::report_fault(e),
350 }
351}
352
353#[derive(Clone)]
359pub struct RuntimeSpawner {
360 pub(crate) shared: Arc<Shared>,
361 pub(crate) chunk: Arc<Chunk>,
362 pub(crate) natives: Arc<NativeTable>,
363}
364
365impl RuntimeSpawner {
366 pub fn spawn(
367 &self,
368 function: u32,
369 args: &[Value],
370 restart_policy: RestartPolicy,
371 ) -> Result<FlowHandle, SpawnError> {
372 spawn_on(
373 &self.shared,
374 &self.chunk,
375 &self.natives,
376 function,
377 args,
378 restart_policy,
379 None,
380 )
381 }
382
383 pub(crate) fn spawn_linked(
384 &self,
385 function: u32,
386 args: &[Value],
387 restart_policy: RestartPolicy,
388 supervisor: SupervisorLink,
389 ) -> Result<FlowHandle, SpawnError> {
390 spawn_on(
391 &self.shared,
392 &self.chunk,
393 &self.natives,
394 function,
395 args,
396 restart_policy,
397 Some(supervisor),
398 )
399 }
400
401 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
402 self.shared.metrics.snapshot()
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use crate::bytecode::{ChunkBuilder, Opcode, Value};
410 use crate::scheduler::FlowOutcome;
411
412 fn add_chunk() -> Chunk {
413 let mut b = ChunkBuilder::new("test");
414 b.begin_function("main", 0, 2);
415 b.emit_load_imm(0, 41);
416 b.emit_load_imm(1, 1);
417 b.emit_binop(Opcode::Add, 0, 0, 1);
418 b.emit_return(0);
419 b.finish()
420 }
421
422 #[test]
423 fn spawn_and_join_add() {
424 let rt = Runtime::with_config(
425 add_chunk(),
426 RuntimeConfig {
427 workers: 1,
428 quantum: 1_000,
429 },
430 )
431 .expect("runtime");
432 let outcome = rt.spawn(0, &[]).expect("spawn").join();
433 rt.shutdown();
434 match outcome {
435 FlowOutcome::Completed(Value::Int(42)) => {}
436 other => panic!("unexpected outcome: {other:?}"),
437 }
438 }
439}