use super::scheduler::KernelPhase;
use super::*;
impl Graph {
pub fn start(&self) -> Result<()> {
self.inner.start()
}
pub fn reset(&self) -> Result<()> {
self.inner.reset()
}
pub fn wait_done(&self) -> Result<()> {
self.inner.wait_done(None)
}
pub fn wait_until_idle(&self) -> Result<()> {
self.inner.wait_until_idle(None)
}
}
impl GraphInner {
pub(super) fn finish(&self, n: NodeId) {
self.schedule_source_resumption(n);
self.schedule_node(n);
self.maybe_close(n);
}
pub(super) fn maybe_close(&self, n: NodeId) -> bool {
let node = &self.nodes[n];
let force = self.shared.has_error() || self.shared.is_cancelled();
{
let mut s = node.sched.lock().expect("scheduler lock poisoned");
if s.close_started || s.in_flight != 0 || !s.opened {
return false;
}
if !force && !node.all_inputs_closed_and_drained() {
return false;
}
s.close_started = true;
}
{
let ctx = unsafe { node.ctx_slot(0) };
ctx.reset();
ctx.close_reason = self.shared.close_reason();
ctx.input_ts = Timestamp::done();
}
let rc = self.call_kernel(n, 0, KernelPhase::Close);
if rc != 0 {
let ctx = unsafe { node.ctx_slot(0) };
let e = ctx.take_error(rc);
ctx.discard_staging();
self.shared.record_error(e);
} else if let Err(e) = self.check_output_types(n, 0) {
unsafe { node.ctx_slot(0) }.discard_staging();
self.shared.record_error(e);
} else {
match self.flush_staging(n, 0) {
Ok(true) => {}
Ok(false) => {
let mut sched = node.sched.lock().expect("scheduler lock poisoned");
sched.blocked_flush = Some(BlockedFlush::Close);
sched.flushing = false;
self.blocked_flush_nodes
.lock()
.expect("blocked flush lock poisoned")
.insert(n);
return false;
}
Err(error) => {
unsafe { node.ctx_slot(0) }.discard_staging();
self.shared.record_error(error);
}
}
}
unsafe { node.ctx_slot(0) }.clear_inputs();
node.sched.lock().expect("scheduler lock poisoned").closed = true;
for &e in &node.outputs {
self.close_edge(e);
}
self.notify_activity();
true
}
pub(super) fn close_edge(&self, edge: EdgeId) {
let e = &self.edges[edge];
if e.closed.swap(true, Ordering::SeqCst) {
return; }
self.publish_bound(edge, Timestamp::done());
for &(node, port) in &e.consumers {
self.nodes[node].input_closed[port].store(true, Ordering::SeqCst);
self.nodes[node].advance_bound(port, Timestamp::done());
self.schedule_node(node);
}
for p in e.pollers.lock().expect("poller list lock poisoned").iter() {
p.closed.store(true, Ordering::SeqCst);
}
}
pub(super) fn set_state_draining_if_all_inputs_closed(&self) {
let all = self.graph_inputs.iter().all(|&e| self.edges[e].is_closed());
if all {
let mut st = self.state.lock().expect("state lock poisoned");
if *st == State::Running {
*st = State::Draining;
}
}
}
pub(super) fn try_advance_closing(&self) -> bool {
let mut progressed = false;
for n in 0..self.nodes.len() {
if self.maybe_close(n) {
progressed = true;
}
}
if !progressed && self.all_nodes_closed() {
self.set_state(State::Terminated);
}
progressed
}
pub(super) fn all_nodes_closed(&self) -> bool {
self.nodes
.iter()
.all(|n| n.sched.lock().expect("scheduler lock poisoned").closed)
}
pub(super) fn remaining(
&self,
deadline: Option<std::time::Instant>,
) -> Option<std::time::Duration> {
const POLL_CAP: std::time::Duration = std::time::Duration::from_millis(50);
match deadline {
None => Some(POLL_CAP),
Some(d) => {
let now = std::time::Instant::now();
if now >= d {
None
} else {
Some((d - now).min(POLL_CAP))
}
}
}
}
pub(super) fn start(self: &Arc<Self>) -> Result<()> {
let st = self.state();
if st != State::Initialized {
return Err(Error::State(format!(
"start can only be called in Initialized (current {st:?})"
)));
}
let provided = self.side_packets.lock().expect("side packet lock poisoned");
for (need, who) in &self.required_side_packets {
if !provided.contains_key(need) {
return Err(Error::InvalidArg(format!(
"missing required side packet `{need}` -- node `{who}` declared it in GetContract; \
inject it with set_side_packet before start"
)));
}
}
let sp = Arc::new(provided.clone());
drop(provided);
for (i, node) in self.nodes.iter().enumerate() {
let already_open = node.sched.lock().expect("scheduler lock poisoned").opened;
for slot in 0..node.max_in_flight {
let ctx = unsafe { node.ctx_slot(slot) };
ctx.side_packets = sp.clone();
ctx.reset();
ctx.input_ts = Timestamp::unstarted();
}
if already_open {
continue; }
let rc = self.call_kernel(i, 0, KernelPhase::Open);
if rc != 0 {
let e = unsafe { node.ctx_slot(0) }.take_error(rc);
self.shared.record_error(e.clone());
return Err(e);
}
node.sched.lock().expect("scheduler lock poisoned").opened = true;
}
self.set_state(State::Running);
self.run_started_us
.store(self.epoch_us().saturating_add(1), Ordering::Relaxed);
*self
.dot_intervals
.lock()
.expect("DOT interval lock poisoned") = dot::DotIntervalBaselines::default();
let weak = Arc::downgrade(self);
for (executor_id, executor) in self.executors.iter().enumerate() {
if self.nodes.iter().any(|node| node.executor == executor_id) {
executor.start(weak.clone());
}
}
for i in 0..self.nodes.len() {
if self.nodes[i].is_source() {
self.schedule_node(i);
}
}
Ok(())
}
pub(super) fn reset(&self) -> Result<()> {
{
let st = *self.state.lock().expect("state lock poisoned");
if st != State::Terminated || !self.is_idle() {
return Err(Error::State(
"reset requires the graph to be Terminated and idle; call wait_done() first"
.into(),
));
}
}
self.shared.reset_run_state();
self.blocked_flush_nodes
.lock()
.expect("blocked flush lock poisoned")
.clear();
for e in &self.edges {
e.closed.store(false, Ordering::SeqCst);
e.dropped.store(0, Ordering::Relaxed);
e.watermark_backpressure.reset();
*e.last_sent.lock().expect("last_sent lock poisoned") = Timestamp::unset();
*e.last_published_bound
.lock()
.expect("published-bound lock poisoned") = Timestamp::unstarted();
for pl in e.pollers.lock().expect("poller list lock poisoned").iter() {
pl.clear(self);
pl.closed.store(false, Ordering::SeqCst);
pl.dropped.store(0, Ordering::Relaxed);
pl.block_backpressure.reset();
}
}
for node in &self.nodes {
{
let mut sc = node.sched.lock().expect("scheduler lock poisoned");
let opened = sc.opened;
*sc = NodeSched::new(node.max_in_flight);
sc.opened = opened;
}
node.stats.reset();
node.kernel.reset_stats();
for q in &node.input_queues {
q.lock().expect("queue lock poisoned").clear();
}
for bytes in &node.input_queue_bytes {
bytes.store(0, Ordering::SeqCst);
}
for reserved in &node.input_queue_reserved {
reserved.store(0, Ordering::SeqCst);
}
for stats in &node.input_queue_stats {
stats.reset();
}
for c in &node.input_closed {
c.store(false, Ordering::SeqCst);
}
for b in &node.input_bounds {
*b.lock().expect("bound lock poisoned") = Timestamp::pre_stream();
}
node.source_done.store(false, Ordering::SeqCst);
node.source_waiting.store(false, Ordering::SeqCst);
node.source_wait_reason.store(0, Ordering::Relaxed);
node.source_wake_deadline_us.store(0, Ordering::Relaxed);
node.source_yield_count.store(0, Ordering::Relaxed);
node.source_wake_generation.fetch_add(1, Ordering::SeqCst);
*node.last_fire.lock().expect("last_fire lock poisoned") = None;
for slot in 0..node.ctxs.len() {
unsafe { node.ctx_slot(slot) }.reset();
}
}
for executor in &self.executors {
executor.reset_run_state();
}
self.in_flight.store(0, Ordering::SeqCst);
self.delegated_cursor.store(0, Ordering::Relaxed);
self.delegated_running.store(false, Ordering::Release);
self.wakeup_pending.store(false, Ordering::Release);
self.wakeup_generation.store(0, Ordering::Release);
{
let mut a = self.activity.0.lock().unwrap_or_else(|e| e.into_inner());
a.waiters = 0;
}
self.paused.store(false, Ordering::SeqCst);
self.run_started_us.store(0, Ordering::Relaxed);
self.e2e_stats.reset();
*self
.dot_intervals
.lock()
.expect("DOT interval lock poisoned") = dot::DotIntervalBaselines::default();
*self.state.lock().expect("state lock poisoned") = State::Initialized;
Ok(())
}
pub(super) fn wait_done(&self, deadline: Option<std::time::Instant>) -> Result<()> {
loop {
while self.pump_step() {}
if self.all_nodes_closed() && self.workers_idle() {
break;
}
let before = self.activity_gen();
if self.workers_idle() {
self.resume_blocked_flushes();
let inputs_open: Vec<&str> = self
.graph_inputs
.iter()
.filter(|&&e| !self.edges[e].is_closed())
.map(|&e| self.edges[e].name.as_str())
.collect();
if !inputs_open.is_empty() {
return Err(Error::State(format!(
"wait_done: graph input ports [{}] still open, the graph won't finish on its own -- \
call close_input/close_all_inputs first",
inputs_open.join(", ")
)));
}
let blocked: Vec<NodeId> = self
.blocked_flush_nodes
.lock()
.expect("blocked flush lock poisoned")
.iter()
.copied()
.collect();
if !blocked.is_empty() {
if self.retry_idle_progress() {
continue;
}
let details = self.backpressure_stall_details(&blocked);
return Err(Error::Kernel(format!(
"wait_done: internal backpressure cannot make progress; blocked queues: [{}]. \
increase the input queue packet capacity or inspect downstream alignment",
details.join("; ")
)));
}
if self.retry_idle_progress() {
continue;
}
if self.try_advance_closing() {
continue;
}
if self.all_nodes_closed() {
break;
}
match self.remaining(deadline) {
Some(duration) => self.wait_activity_since(before, duration),
None => return Err(Error::Timeout),
}
if self.activity_gen() != before || !self.workers_idle() {
continue;
}
if self.remaining(deadline).is_none() {
return Err(Error::Timeout);
}
let stuck: Vec<&str> = (0..self.nodes.len())
.filter(|&n| {
!self.nodes[n]
.sched
.lock()
.expect("scheduler lock poisoned")
.closed
})
.map(|n| self.nodes[n].name.as_str())
.collect();
return Err(Error::Kernel(format!(
"wait_done: all inputs closed but the graph is still idle, nodes not closed: [{}]. \
usually some kernel's output/close condition is unmet (use dump to inspect queue backlog)",
stuck.join(", ")
)));
}
match self.remaining(deadline) {
Some(d) => {
self.wait_activity_since(before, d);
}
None => return Err(Error::Timeout),
}
}
if self.all_nodes_closed() {
self.set_state(State::Terminated);
}
if self.shared.is_cancelled() {
return Err(Error::Cancelled);
}
match self.shared.first_error() {
Some(e) => Err(e),
None => Ok(()),
}
}
pub(super) fn wait_until_idle(&self, deadline: Option<std::time::Instant>) -> Result<()> {
loop {
while self.run_one_main_task() {}
self.resume_blocked_flushes();
let before = self.activity_gen();
if self.is_idle() {
break;
}
if self.workers_idle() {
let blocked: Vec<NodeId> = self
.blocked_flush_nodes
.lock()
.expect("blocked flush lock poisoned")
.iter()
.copied()
.collect();
if !blocked.is_empty() && self.retry_idle_progress() {
continue;
}
if self.is_idle() {
break;
}
if !blocked.is_empty() {
let details = self.backpressure_stall_details(&blocked);
return Err(Error::Kernel(format!(
"wait_until_idle: internal backpressure cannot make progress; blocked queues: [{}]",
details.join("; ")
)));
}
}
match self.remaining(deadline) {
Some(d) => {
self.wait_activity_since(before, d);
}
None => return Err(Error::Timeout),
}
}
match self.shared.first_error() {
Some(e) => Err(e),
None => Ok(()),
}
}
}
impl Drop for GraphInner {
fn drop(&mut self) {
self.shutdown_executors();
for n in 0..self.nodes.len() {
let need_close = {
let s = self.nodes[n].sched.lock().expect("scheduler lock poisoned");
s.opened && !s.close_started
};
if !need_close {
continue;
}
{
let ctx = unsafe { self.nodes[n].ctx_slot(0) };
ctx.reset();
ctx.close_reason = self.shared.close_reason();
ctx.input_ts = Timestamp::done();
}
let rc = self.call_kernel(n, 0, KernelPhase::Close);
if rc != 0 {
runtime::log_warn(&format!(
"node `{}`: close returned {rc} during graph destruction (ignored)",
self.nodes[n].name
));
}
unsafe { self.nodes[n].ctx_slot(0) }.clear_inputs();
self.nodes[n]
.sched
.lock()
.expect("scheduler lock poisoned")
.closed = true;
}
}
}