Skip to content

feat(graph): BarrierRelief primitive for mixed conditional/unconditional fan-in#62

Merged
graycyrus merged 3 commits into
mainfrom
fix/eng5-barrier-relief
Jul 17, 2026
Merged

feat(graph): BarrierRelief primitive for mixed conditional/unconditional fan-in#62
graycyrus merged 3 commits into
mainfrom
fix/eng5-barrier-relief

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds GraphBuilder::add_barrier_relief(source, relief_node, barrier_node), a new primitive for the durable graph's waiting/barrier (fan-in) mechanism.

A waiting/barrier node ([add_waiting_edge]) normally activates only once every registered predecessor has arrived — possibly across supersteps. That's correct for a plain diamond or parallel fan-out, but breaks for a mixed fan-in: one predecessor unconditionally reachable, another reachable only via a conditional branch. If the brancher doesn't take that branch, the conditional predecessor never arrives and the barrier deadlocks forever.

The naive fix — lowering that predecessor's edge as plain instead of waiting — reintroduces a different, worse bug: when the branch IS taken, the merge can fire off the superstep snapshot before that predecessor's items commit, silently dropping them (a stale-snapshot data race).

add_barrier_relief fixes the deadlock without weakening the barrier:

  • When source completes a superstep, the executor resolves source's actual routing target(s) this step and walks them forward through the compiled graph's deterministic (single-successor) edges to see whether relief_node is reachable before barrier_node (this also correctly handles a multi-hop conditional predecessor, e.g. source --branch--> x --main--> relief_node, not just a direct one-hop edge).
  • If NOT reachable (the branch leading to relief_node was not taken), it registers a phantom arrival of relief_node at barrier_node, letting the barrier clear on its remaining real predecessors.
  • If reachable (the branch WAS taken), no phantom is registered — the barrier waits for relief_node's real arrival, whenever it lands, so no data race is possible.

Non-breaking

Purely additive:

  • New public BarrierRelief struct and GraphBuilder::add_barrier_relief method.
  • New barrier_reliefs: Arc<Vec<BarrierRelief>> field on CompiledGraph, defaulting to empty when unused — every existing caller of GraphBuilder/CompiledGraph::from_parts compiles and behaves identically since no existing graph registers a relief.
  • No checkpoint/serialization format changes: reliefs are compiled-graph metadata (not per-run state); the existing barrier_arrivals persistence already covers the runtime side.

Tests

  • barrier_relief_fires_when_source_skips_relief_node — direct (one-hop) case: a barrier relieved when the brancher never routes to the relief node.
  • Full existing suite green (1128 lib tests, all integration binaries, 46 doctests).

Context

Companion fix for a tinyflows engine bug (mixed/multi-hop conditional fan-in deadlock — Eng §5). The tinyflows PR building on this primitive follows once this merges.

Summary by CodeRabbit

  • New Features
    • Added builder support for registering “barrier reliefs” tied to conditional routing, enabling mixed fan-in barrier coordination.
  • Bug Fixes
    • Improved barrier scheduling in mixed fan-in scenarios by correctly accounting for conditional routes that are skipped.
    • Added logic to avoid unintended relief-node arrivals/updates when conditional paths are not taken.
    • Added a unit test covering parallel mixed fan-in behavior with skipped conditional predecessors.

A waiting/barrier node (add_waiting_edge) normally activates only once
every registered predecessor has arrived. When one predecessor is only
reachable via a conditional branch, and the brancher routes elsewhere,
that predecessor never arrives and the barrier deadlocks forever.

Add GraphBuilder::add_barrier_relief(source, relief_node, barrier_node):
when `source` completes a superstep without routing to `relief_node`,
route_completed registers a phantom arrival of `relief_node` at
`barrier_node`, letting the barrier clear on its remaining predecessors
instead of hanging. This fixes the deadlock without weakening the
barrier into a plain edge, which would let a *taken* branch's data race
past the merge and be silently dropped (a stale-snapshot data loss bug).

Threaded through GraphBuilder -> CompiledGraph::from_parts (compiled-
graph metadata; no checkpoint/serialization changes needed since
barrier_arrivals already persist). Adds
barrier_relief_fires_when_source_skips_relief_node covering the fix.

Part of the tinyflows Eng §5 mixed fan-in deadlock fix (companion change
in tinyflows/src/engine.rs).
…uperstep scheduling

barrier_relief_fires_when_source_skips_relief_node covered only the
direct (one-hop) case: `source --branch--> relief_node`. The check —
"is relief_node freshly scheduled into next this step" — is a same-
superstep proxy that breaks for a multi-hop conditional predecessor
(`source --branch--> x --main--> relief_node`, x a plain pass-through):
relief_node is not in next on the step source itself completes, even on
the TAKEN branch, since x hasn't run x yet. The relief fired a premature
phantom arrival regardless, letting the barrier clear before the real
predecessor's data ever committed — reintroducing the exact data-loss
bug BarrierRelief exists to prevent.

Fix: resolve source's actual routing target(s) this step (reusing the
existing pure self.route()) and walk them forward through the compiled
graph's deterministic (single-successor) edges via a new
reaches_deterministically helper, stopping at barrier_node. This answers
"was the branch leading to relief_node taken" directly, correct for any
chain length of plain/waiting-edge pass-throughs — not just one hop.
Keeps the same-superstep next_seen check as a defensive fallback.

A further conditional/command node between source and relief_node (no
deterministic self.edges entry) is a second runtime decision this walk
cannot resolve ahead of time and conservatively reports unreachable —
documented as a residual limitation in the new helper's doc comment.
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0eefb15f-f5d9-43ff-a56c-1e6cca88c456

📥 Commits

Reviewing files that changed from the base of the PR and between f60135b and a7b3d19.

📒 Files selected for processing (1)
  • src/graph/compiled/routing.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/graph/compiled/routing.rs

📝 Walkthrough

Walkthrough

Adds barrier relief registration to graph building and compiled graph storage. Completion routing recognizes conditionally skipped predecessors, records phantom arrivals, and can activate mixed fan-in barriers without executing skipped relief nodes.

Changes

Barrier relief support

Layer / File(s) Summary
Register barrier reliefs
src/graph/builder/mod.rs, src/graph/builder/types.rs
Defines BarrierRelief, stores registrations in GraphBuilder, and forwards them during compilation.
Retain reliefs in compiled graphs
src/graph/compiled/mod.rs, src/graph/compiled/types.rs
Stores barrier relief registrations in CompiledGraph and preserves them when cloning.
Resolve skipped predecessors
src/graph/compiled/routing.rs, src/graph/compiled/test.rs
Detects deterministic branch reachability, records phantom arrivals, and tests activation of a barrier whose conditional predecessor is skipped.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GraphBuilder
  participant CompiledGraph
  participant route_completed
  participant Barrier
  GraphBuilder->>CompiledGraph: compile barrier relief registrations
  CompiledGraph->>route_completed: provide stored relief configuration
  route_completed->>route_completed: resolve source routing and reachability
  route_completed->>Barrier: register phantom arrival and schedule activation
Loading

Poem

I’m a rabbit with hops through the graph’s waiting gate,
A skipped node needn’t make the barrier wait.
Phantom arrivals quietly join the queue,
While real updates keep the state true.
Mixed fan-in now springs along—
And I thump my paws to celebrate this song!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main addition: a BarrierRelief primitive for mixed conditional/unconditional fan-in.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/graph/compiled/routing.rs`:
- Around line 87-114: Cache the targets returned by the first routing pass in
the relevant activation-processing flow, keyed by activation/index, and reuse
them in the barrier-relief loop around barrier_reliefs and
reaches_deterministically instead of calling self.route again. Preserve the
existing branch-taken and relief behavior, and add a regression test verifying a
stateful or nondeterministic router is invoked only once per activation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1d430cf0-d6a0-4f1f-9594-09ac056625f7

📥 Commits

Reviewing files that changed from the base of the PR and between 674d89f and f60135b.

📒 Files selected for processing (6)
  • src/graph/builder/mod.rs
  • src/graph/builder/types.rs
  • src/graph/compiled/mod.rs
  • src/graph/compiled/routing.rs
  • src/graph/compiled/test.rs
  • src/graph/compiled/types.rs

Comment thread src/graph/compiled/routing.rs Outdated
Addresses @coderabbitai on PR #62 (routing.rs:87-114): the barrier-relief
pass called self.route() a second time for the same activation to get
its resolved targets, which would invoke a stateful/nondeterministic
router closure twice for one activation. Cache each activation's
resolved targets (already computed once in the main completed loop) and
have the relief pass index into that cache instead of recomputing.
@graycyrus
graycyrus merged commit 9bde74f into main Jul 17, 2026
3 checks passed
@graycyrus
graycyrus deleted the fix/eng5-barrier-relief branch July 17, 2026 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant