lmflow/graph/
lifecycle.rs1use super::scheduler::KernelPhase;
2use super::*;
3
4impl Graph {
7 pub fn start(&self) -> Result<()> {
10 self.inner.start()
11 }
12
13 pub fn reset(&self) -> Result<()> {
14 self.inner.reset()
15 }
16
17 pub fn wait_done(&self) -> Result<()> {
18 self.inner.wait_done(None)
19 }
20
21 pub fn wait_until_idle(&self) -> Result<()> {
22 self.inner.wait_until_idle(None)
23 }
24}
25
26impl GraphInner {
27 pub(super) fn finish(&self, n: NodeId) {
29 self.schedule_source_resumption(n);
30 self.schedule_node(n);
31 self.maybe_close(n);
32 }
33
34 pub(super) fn maybe_close(&self, n: NodeId) -> bool {
36 let node = &self.nodes[n];
37 let force = self.shared.has_error() || self.shared.is_cancelled();
38
39 {
43 let mut s = node.sched.lock().expect("scheduler lock poisoned");
44 if s.close_started || s.in_flight != 0 || !s.opened {
45 return false;
46 }
47 if !force && !node.all_inputs_closed_and_drained() {
48 return false;
49 }
50 s.close_started = true;
51 }
52
53 if force {
54 self.discard_queued_inputs(n);
55 }
56
57 {
59 let ctx = unsafe { node.ctx_slot(0) };
60 ctx.reset();
61 ctx.close_reason = self.shared.close_reason();
62 ctx.input_ts = Timestamp::done();
63 }
64 let rc = self.call_kernel(n, 0, KernelPhase::Close);
65 if rc != 0 {
66 let ctx = unsafe { node.ctx_slot(0) };
67 let e = ctx.take_error(rc);
68 ctx.discard_staging();
69 self.shared.record_error(e);
70 } else if let Err(e) = self.check_output_types(n, 0) {
71 unsafe { node.ctx_slot(0) }.discard_staging();
72 self.shared.record_error(e);
73 } else {
74 match self.flush_staging(n, 0) {
75 Ok(true) => {}
76 Ok(false) => {
77 let mut sched = node.sched.lock().expect("scheduler lock poisoned");
78 sched.blocked_flush = Some(BlockedFlush::Close);
79 sched.flushing = false;
80 self.blocked_flush_nodes
81 .lock()
82 .expect("blocked flush lock poisoned")
83 .insert(n);
84 return false;
85 }
86 Err(error) => {
87 unsafe { node.ctx_slot(0) }.discard_staging();
88 self.shared.record_error(error);
89 }
90 }
91 }
92 unsafe { node.ctx_slot(0) }.clear_inputs();
93 node.sched.lock().expect("scheduler lock poisoned").closed = true;
94
95 for &e in &node.outputs {
96 self.close_edge(e);
97 }
98 self.notify_activity();
99 true
100 }
101
102 fn discard_queued_inputs(&self, n: NodeId) {
105 let node = &self.nodes[n];
106 for (port, queue) in node.input_queues.iter().enumerate() {
107 let discarded = {
108 let mut queue = queue.lock().expect("queue lock poisoned");
109 let discarded = std::mem::take(&mut *queue);
110 node.input_queue_bytes[port].store(0, Ordering::SeqCst);
111 for packet in &discarded {
112 self.shared.on_dequeue(packet.byte_size());
113 }
114 discarded
115 };
116 drop(discarded);
118 }
119 self.notify_activity();
120 }
121
122 pub(super) fn close_edge(&self, edge: EdgeId) {
123 let e = &self.edges[edge];
124 let _ingress = if e.is_graph_input {
125 let defer_if_busy = self
129 .graph_inputs
130 .iter()
131 .any(|&input| self.edges[input].is_ingress_owner());
132 match e.enter_ingress_close(defer_if_busy) {
133 Some(guard) => Some(guard),
134 None => return, }
136 } else {
137 None
138 };
139 self.close_edge_committed(edge);
140 }
141
142 pub(super) fn close_edge_committed(&self, edge: EdgeId) {
143 let e = &self.edges[edge];
144 if e.closed.swap(true, Ordering::SeqCst) {
145 return; }
147 self.publish_bound(edge, Timestamp::done());
148 for &(node, port) in &e.consumers {
149 self.nodes[node].input_closed[port].store(true, Ordering::SeqCst);
150 self.nodes[node].advance_bound(port, Timestamp::done());
152 self.schedule_node(node);
154 }
155 for p in e.pollers.lock().expect("poller list lock poisoned").iter() {
157 p.closed.store(true, Ordering::SeqCst);
158 }
159 self.notify_activity();
162 self.request_wakeup();
163 }
164
165 pub(super) fn set_state_draining_if_all_inputs_closed(&self) {
166 let all = self.graph_inputs.iter().all(|&e| self.edges[e].is_closed());
167 if all {
168 let mut st = self.state.lock().expect("state lock poisoned");
169 if *st == State::Running {
170 *st = State::Draining;
171 }
172 }
173 }
174
175 pub(super) fn try_advance_closing(&self) -> bool {
177 let mut progressed = false;
178 for n in 0..self.nodes.len() {
179 if self.maybe_close(n) {
180 progressed = true;
181 }
182 }
183 if !progressed && self.all_nodes_closed() {
184 self.set_state(State::Terminated);
185 }
186 progressed
187 }
188
189 pub(super) fn all_nodes_closed(&self) -> bool {
190 self.nodes
191 .iter()
192 .all(|n| n.sched.lock().expect("scheduler lock poisoned").closed)
193 }
194
195 pub(super) fn remaining(
198 &self,
199 deadline: Option<std::time::Instant>,
200 ) -> Option<std::time::Duration> {
201 const POLL_CAP: std::time::Duration = std::time::Duration::from_millis(50);
202 match deadline {
203 None => Some(POLL_CAP),
204 Some(d) => {
205 let now = std::time::Instant::now();
206 if now >= d {
207 None
208 } else {
209 Some((d - now).min(POLL_CAP))
210 }
211 }
212 }
213 }
214 pub(super) fn start(self: &Arc<Self>) -> Result<()> {
215 let st = self.state();
216 if st != State::Initialized {
217 return Err(Error::State(format!(
218 "start can only be called in Initialized (current {st:?})"
219 )));
220 }
221
222 if let Some(error) = self.shared.first_error() {
225 return Err(error);
226 }
227 if self.shared.is_cancelled() {
228 return Err(Error::Cancelled);
229 }
230
231 let provided = self.side_packets.lock().expect("side packet lock poisoned");
233 for (need, who) in &self.required_side_packets {
234 if !provided.contains_key(need) {
235 return Err(Error::InvalidArg(format!(
236 "missing required side packet `{need}` -- node `{who}` declared it in GetContract; \
237 inject it with set_side_packet before start"
238 )));
239 }
240 }
241 let sp = Arc::new(provided.clone());
242 drop(provided);
243
244 let mut open_outputs = Vec::new();
248 for (i, node) in self.nodes.iter().enumerate() {
249 let already_open = node.sched.lock().expect("scheduler lock poisoned").opened;
250 for slot in 0..node.max_in_flight {
251 let ctx = unsafe { node.ctx_slot(slot) };
253 ctx.side_packets = sp.clone();
254 ctx.reset();
255 ctx.input_ts = Timestamp::unstarted();
256 }
257 if already_open {
258 continue; }
260 let rc = self.call_kernel(i, 0, KernelPhase::Open);
261 if rc != 0 {
262 let e = unsafe { node.ctx_slot(0) }.take_error(rc);
263 for node in &self.nodes {
264 unsafe { node.ctx_slot(0) }.discard_staging();
265 }
266 self.shared.record_error(e.clone());
267 return Err(e);
268 }
269 node.sched.lock().expect("scheduler lock poisoned").opened = true;
270 if let Err(e) = self.check_output_types(i, 0) {
271 for node in &self.nodes {
272 unsafe { node.ctx_slot(0) }.discard_staging();
273 }
274 self.shared.record_error(e.clone());
275 return Err(e);
276 }
277 let ctx = unsafe { node.ctx_slot(0) };
278 if ctx.staging.iter().any(|packets| !packets.is_empty())
279 || ctx.next_bounds.iter().any(Option::is_some)
280 {
281 open_outputs.push(i);
282 }
283 }
284
285 for &i in &open_outputs {
288 let mut sched = self.nodes[i].sched.lock().expect("scheduler lock poisoned");
289 sched.free_slots.retain(|&slot| slot != 0);
290 sched.in_flight += 1;
291 sched.blocked_flush = Some(BlockedFlush::Open);
292 self.blocked_flush_nodes
293 .lock()
294 .expect("blocked flush lock poisoned")
295 .insert(i);
296 }
297 self.set_state(State::Running);
298 self.run_started_us
299 .store(self.epoch_us().saturating_add(1), Ordering::Relaxed);
300 *self
301 .dot_intervals
302 .lock()
303 .expect("DOT interval lock poisoned") = dot::DotIntervalBaselines::default();
304
305 let weak = Arc::downgrade(self);
307 for (executor_id, executor) in self.executors.iter().enumerate() {
308 if self.nodes.iter().any(|node| node.executor == executor_id) {
309 executor.start(weak.clone());
310 }
311 }
312 self.resume_blocked_flushes();
313 for i in 0..self.nodes.len() {
316 if self.nodes[i].is_source() {
317 self.schedule_node(i);
318 }
319 }
320 Ok(())
321 }
322
323 pub(super) fn reset(&self) -> Result<()> {
332 {
335 let st = *self.state.lock().expect("state lock poisoned");
336 if st != State::Terminated || !self.is_idle() {
337 return Err(Error::State(
338 "reset requires the graph to be Terminated and idle; call wait_done() first"
339 .into(),
340 ));
341 }
342 }
343
344 self.shared.reset_run_state();
347 self.blocked_flush_nodes
348 .lock()
349 .expect("blocked flush lock poisoned")
350 .clear();
351
352 for e in &self.edges {
355 e.reset_ingress();
356 e.closed.store(false, Ordering::SeqCst);
357 e.dropped.store(0, Ordering::Relaxed);
358 e.watermark_backpressure.reset();
359 *e.last_sent.lock().expect("last_sent lock poisoned") = Timestamp::unset();
360 *e.last_published_bound
361 .lock()
362 .expect("published-bound lock poisoned") = Timestamp::unstarted();
363 for pl in e.pollers.lock().expect("poller list lock poisoned").iter() {
366 pl.clear(self);
367 pl.closed.store(false, Ordering::SeqCst);
368 pl.dropped.store(0, Ordering::Relaxed);
369 pl.block_backpressure.reset();
370 }
371 }
372
373 for node in &self.nodes {
375 {
378 let mut sc = node.sched.lock().expect("scheduler lock poisoned");
379 let opened = sc.opened;
380 *sc = NodeSched::new(node.max_in_flight);
381 sc.opened = opened;
382 }
383 node.stats.reset();
384 node.kernel.reset_stats();
385 for q in &node.input_queues {
386 q.lock().expect("queue lock poisoned").clear();
387 }
388 for bytes in &node.input_queue_bytes {
389 bytes.store(0, Ordering::SeqCst);
390 }
391 for reserved in &node.input_queue_reserved {
392 reserved.store(0, Ordering::SeqCst);
393 }
394 for stats in &node.input_queue_stats {
395 stats.reset();
396 }
397 for c in &node.input_closed {
398 c.store(false, Ordering::SeqCst);
399 }
400 for b in &node.input_bounds {
403 *b.lock().expect("bound lock poisoned") = Timestamp::pre_stream();
404 }
405 node.source_done.store(false, Ordering::SeqCst);
406 node.source_waiting.store(false, Ordering::SeqCst);
407 node.source_wait_reason.store(0, Ordering::Relaxed);
408 node.source_wake_deadline_us.store(0, Ordering::Relaxed);
409 node.source_yield_count.store(0, Ordering::Relaxed);
410 node.source_wake_generation.fetch_add(1, Ordering::SeqCst);
411 *node.last_fire.lock().expect("last_fire lock poisoned") = None;
412 for slot in 0..node.ctxs.len() {
414 unsafe { node.ctx_slot(slot) }.reset();
415 }
416 }
417
418 for executor in &self.executors {
421 executor.reset_run_state();
422 }
423 self.in_flight.store(0, Ordering::SeqCst);
424 self.delegated_cursor.store(0, Ordering::Relaxed);
425 self.delegated_running.store(false, Ordering::Release);
426 self.wakeup_pending.store(false, Ordering::Release);
427 self.wakeup_generation.store(0, Ordering::Release);
428 {
429 let mut a = self.activity.0.lock().unwrap_or_else(|e| e.into_inner());
430 a.waiters = 0;
431 }
432 self.paused.store(false, Ordering::SeqCst);
433 self.run_started_us.store(0, Ordering::Relaxed);
434 self.e2e_stats.reset();
435 *self
436 .dot_intervals
437 .lock()
438 .expect("DOT interval lock poisoned") = dot::DotIntervalBaselines::default();
439
440 *self.state.lock().expect("state lock poisoned") = State::Initialized;
442 Ok(())
443 }
444
445 pub(super) fn wait_done(&self, deadline: Option<std::time::Instant>) -> Result<()> {
450 loop {
451 while self.remaining(deadline).is_some() && self.pump_step() {}
455 if self.all_nodes_closed() && self.workers_idle() {
456 break;
457 }
458 if self.remaining(deadline).is_none() {
459 return Err(Error::Timeout);
460 }
461 let before = self.activity_gen();
463 if self.workers_idle() {
464 self.resume_blocked_flushes();
465 let inputs_open: Vec<&str> = self
469 .graph_inputs
470 .iter()
471 .filter(|&&e| !self.edges[e].is_closed())
472 .map(|&e| self.edges[e].name.as_str())
473 .collect();
474 if !inputs_open.is_empty() {
475 return Err(Error::State(format!(
476 "wait_done: graph input ports [{}] still open, the graph won't finish on its own -- \
477 call close_input/close_all_inputs first",
478 inputs_open.join(", ")
479 )));
480 }
481 let blocked: Vec<NodeId> = self
482 .blocked_flush_nodes
483 .lock()
484 .expect("blocked flush lock poisoned")
485 .iter()
486 .copied()
487 .collect();
488 if !blocked.is_empty() {
489 if self.retry_idle_progress(deadline)? {
490 continue;
491 }
492 let details = self.backpressure_stall_details(&blocked);
493 return Err(Error::Kernel(format!(
494 "wait_done: internal backpressure cannot make progress; blocked queues: [{}]. \
495 increase the input queue packet capacity or inspect downstream alignment",
496 details.join("; ")
497 )));
498 }
499 if self.retry_idle_progress(deadline)? {
500 continue;
501 }
502 if self.try_advance_closing() {
504 continue;
505 }
506 if self.all_nodes_closed() {
511 break;
512 }
513 match self.remaining(deadline) {
516 Some(duration) => self.wait_activity_since(before, duration),
517 None => return Err(Error::Timeout),
518 }
519 if self.activity_gen() != before || !self.workers_idle() {
520 continue;
521 }
522 if self.remaining(deadline).is_none() {
523 return Err(Error::Timeout);
524 }
525 let stuck: Vec<&str> = (0..self.nodes.len())
526 .filter(|&n| {
527 !self.nodes[n]
528 .sched
529 .lock()
530 .expect("scheduler lock poisoned")
531 .closed
532 })
533 .map(|n| self.nodes[n].name.as_str())
534 .collect();
535 return Err(Error::Kernel(format!(
536 "wait_done: all inputs closed but the graph is still idle, nodes not closed: [{}]. \
537 usually some kernel's output/close condition is unmet (use dump to inspect queue backlog)",
538 stuck.join(", ")
539 )));
540 }
541 match self.remaining(deadline) {
543 Some(d) => {
544 self.wait_activity_since(before, d);
545 }
546 None => return Err(Error::Timeout),
547 }
548 }
549 if self.all_nodes_closed() {
550 self.set_state(State::Terminated);
551 }
552 if self.shared.is_cancelled() {
553 return Err(Error::Cancelled);
554 }
555 match self.shared.first_error() {
556 Some(e) => Err(e),
557 None => Ok(()),
558 }
559 }
560
561 pub(super) fn wait_until_idle(&self, deadline: Option<std::time::Instant>) -> Result<()> {
563 loop {
564 while self.remaining(deadline).is_some() && self.run_one_main_task() {}
565 self.resume_blocked_flushes();
566 let before = self.activity_gen();
568 if self.is_idle() {
569 break;
570 }
571 if self.remaining(deadline).is_none() {
572 return Err(Error::Timeout);
573 }
574 if self.workers_idle() {
575 let blocked: Vec<NodeId> = self
576 .blocked_flush_nodes
577 .lock()
578 .expect("blocked flush lock poisoned")
579 .iter()
580 .copied()
581 .collect();
582 if !blocked.is_empty() && self.retry_idle_progress(deadline)? {
583 continue;
584 }
585 if self.is_idle() {
586 break;
587 }
588 if !blocked.is_empty() {
589 let details = self.backpressure_stall_details(&blocked);
590 return Err(Error::Kernel(format!(
591 "wait_until_idle: internal backpressure cannot make progress; blocked queues: [{}]",
592 details.join("; ")
593 )));
594 }
595 }
596 match self.remaining(deadline) {
597 Some(d) => {
598 self.wait_activity_since(before, d);
599 }
600 None => return Err(Error::Timeout),
601 }
602 }
603 match self.shared.first_error() {
604 Some(e) => Err(e),
605 None => Ok(()),
606 }
607 }
608}
609
610impl Drop for GraphInner {
611 fn drop(&mut self) {
614 self.shutdown_executors();
616
617 for n in 0..self.nodes.len() {
618 let need_close = {
619 let s = self.nodes[n].sched.lock().expect("scheduler lock poisoned");
620 s.opened && !s.close_started
621 };
622 if !need_close {
623 continue;
624 }
625 {
626 let ctx = unsafe { self.nodes[n].ctx_slot(0) };
628 ctx.reset();
629 ctx.close_reason = self.shared.close_reason();
630 ctx.input_ts = Timestamp::done();
631 }
632 let rc = self.call_kernel(n, 0, KernelPhase::Close);
633 if rc != 0 {
634 runtime::log_warn(&format!(
635 "node `{}`: close returned {rc} during graph destruction (ignored)",
636 self.nodes[n].name
637 ));
638 }
639 unsafe { self.nodes[n].ctx_slot(0) }.clear_inputs();
640 self.nodes[n]
641 .sched
642 .lock()
643 .expect("scheduler lock poisoned")
644 .closed = true;
645 }
646 }
647}