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