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};
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(_)) => Err(SendError::MailboxFull(target)),
268 Err(e) => {
269 super::error::report_fault(e);
270 Err(SendError::NoSuchFlow(target))
271 }
272 }
273 }
274
275 pub fn shutdown(mut self) {
282 self.shared.shutdown.store(true, Ordering::Release);
283 self.shared.timer.shutdown();
284 {
285 let (lock, cvar) = &self.shared.notify;
286 match super::sync_lock::lock(lock, "Runtime::shutdown") {
287 Ok(_g) => cvar.notify_all(),
288 Err(e) => super::error::report_fault(e),
289 }
290 }
291 for w in self.workers.drain(..) {
292 let _ = w.join();
293 }
294 if let Some(t) = self.timer_thread.take() {
295 let _ = t.join();
296 }
297 }
298}
299
300pub fn flow_id_from_u64(raw: u64) -> FlowId {
304 FlowId(raw)
305}
306
307#[derive(Debug, Clone, PartialEq, Eq)]
309pub enum SendError {
310 NoSuchFlow(FlowId),
311 NotAHop { got: &'static str },
313 MailboxFull(FlowId),
315}
316
317impl std::fmt::Display for SendError {
318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
319 match self {
320 SendError::NoSuchFlow(id) => write!(f, "no live flow {id}"),
321 SendError::NotAHop { got } => {
322 write!(f, "atomic hop requires Value::Message, got {got}")
323 }
324 SendError::MailboxFull(id) => write!(f, "mailbox full for {id}"),
325 }
326 }
327}
328
329impl std::error::Error for SendError {}
330
331pub(crate) fn spawn_on(
340 shared: &Arc<Shared>,
341 chunk: &Arc<Chunk>,
342 natives: &Arc<NativeTable>,
343 function: u32,
344 args: &[Value],
345 restart_policy: RestartPolicy,
346 supervisor: Option<SupervisorLink>,
347) -> Result<FlowHandle, SpawnError> {
348 let id = super::process::next_flow_id();
349 let vm = Vm::new(chunk.clone(), natives.clone(), function, args)?;
350 let mailbox = Arc::new(Mailbox::with_config(shared.mailbox));
351 if let Err(e) = shared.directory.register(id, mailbox.clone()) {
352 super::error::report_fault(e);
353 return Err(SpawnError::VmInit(
354 "directory register failed (poisoned lock)".into(),
355 ));
356 }
357 let (tx, rx) = super::oneshot::channel();
358 let mut flow = Box::new(Flow::new(id, vm, mailbox, restart_policy, tx));
359 flow.supervisor = supervisor;
360 RuntimeMetrics::inc(&shared.metrics.processes_spawned);
361 shared.injector.push(flow);
362 wake_workers(shared);
363 Ok(FlowHandle { id, receiver: rx })
364}
365
366pub(crate) fn wake_workers(shared: &Shared) {
367 let (lock, cvar) = &shared.notify;
368 match super::sync_lock::lock(lock, "wake_workers") {
369 Ok(_g) => cvar.notify_one(),
370 Err(e) => super::error::report_fault(e),
371 }
372}
373
374#[derive(Clone)]
380pub struct RuntimeSpawner {
381 pub(crate) shared: Arc<Shared>,
382 pub(crate) chunk: Arc<Chunk>,
383 pub(crate) natives: Arc<NativeTable>,
384}
385
386impl RuntimeSpawner {
387 pub fn spawn(
388 &self,
389 function: u32,
390 args: &[Value],
391 restart_policy: RestartPolicy,
392 ) -> Result<FlowHandle, SpawnError> {
393 spawn_on(
394 &self.shared,
395 &self.chunk,
396 &self.natives,
397 function,
398 args,
399 restart_policy,
400 None,
401 )
402 }
403
404 pub(crate) fn spawn_linked(
405 &self,
406 function: u32,
407 args: &[Value],
408 restart_policy: RestartPolicy,
409 supervisor: SupervisorLink,
410 ) -> Result<FlowHandle, SpawnError> {
411 spawn_on(
412 &self.shared,
413 &self.chunk,
414 &self.natives,
415 function,
416 args,
417 restart_policy,
418 Some(supervisor),
419 )
420 }
421
422 pub fn metrics(&self) -> RuntimeMetricsSnapshot {
423 self.shared.metrics.snapshot()
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use crate::bytecode::{ChunkBuilder, Opcode, Value};
431 use crate::scheduler::FlowOutcome;
432
433 fn add_chunk() -> Chunk {
434 let mut b = ChunkBuilder::new("test");
435 b.begin_function("main", 0, 2);
436 b.emit_load_imm(0, 41);
437 b.emit_load_imm(1, 1);
438 b.emit_binop(Opcode::Add, 0, 0, 1);
439 b.emit_return(0);
440 b.finish()
441 }
442
443 #[test]
444 fn spawn_and_join_add() -> Result<(), Box<dyn std::error::Error>> {
445 let rt = Runtime::with_config(
446 add_chunk(),
447 RuntimeConfig {
448 workers: 1,
449 quantum: 1_000,
450 mailbox: MailboxConfig::DEFAULT,
451 },
452 )?;
453 let outcome = rt.spawn(0, &[])?.join();
454 rt.shutdown();
455 match outcome {
456 FlowOutcome::Completed(Value::Int(42)) => Ok(()),
457 other => Err(format!("unexpected outcome: {other:?}").into()),
458 }
459 }
460}