Skip to content

Commit 44ca66c

Browse files
selection: test deduplication precludes over-delivery
Summary: this test captures a key structural guarantee of the routing system: that overdelivery is prevented not by explicit guards, but by the semantics of the traversal combined with frame deduplication. the comment explains that when multiple delivery frames reach the same destination, they must have the same routing lineage and state — making them structurally equivalent. as a result, deduplication ensures only one of them is retained. by disabling deduplication, the test demonstrates that overdelivery becomes possible, highlighting the value of this mechanism. i believe this is helpful both as regression protection and as executable documentation of a subtle invariant. Reviewed By: moonli Differential Revision: D75027898 fbshipit-source-id: 2a9eed9056e03e6c7d2c3c99db6344def4d9a0dc
1 parent 903a4e4 commit 44ca66c

File tree

2 files changed

+110
-2
lines changed

2 files changed

+110
-2
lines changed

ndslice/src/selection/routing.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,7 @@ mod tests {
945945
use crate::selection::dsl::*;
946946
use crate::selection::test_utils::RoutedMessage;
947947
use crate::selection::test_utils::collect_routed_nodes;
948+
use crate::selection::test_utils::collect_routed_paths;
948949
use crate::shape;
949950

950951
// A test slice: (zones = 2, hosts = 4, gpus = 8).
@@ -1621,4 +1622,78 @@ mod tests {
16211622
// Inner selection should still be All(All(True))
16221623
assert!(matches!(hop.selection, Selection::All(_)));
16231624
}
1625+
1626+
// This test relies on a deep structural property of the routing
1627+
// semantics:
1628+
//
1629+
// Overdelivery is prevented not by ad hoc guards, but by the
1630+
// structure of the traversal itself — particularly in the
1631+
// presence of routing frame deduplication.
1632+
//
1633+
// When a frame reaches the final dimension with `selection ==
1634+
// True`, it becomes a delivery frame. If multiple such frames
1635+
// target the same coordinate, then:
1636+
//
1637+
// - They must share the same coordinate `here`
1638+
// - They must have reached it via the same routing path (by the
1639+
// Unique Path Theorem)
1640+
// - Their `RoutingFrame` state is thus structurally identical:
1641+
// - Same `here`
1642+
// - Same `dim` (equal to `slice.num_dim()`)
1643+
// - Same residual `selection == True`
1644+
//
1645+
// The deduplication logic (via `RoutingFrameKey`) collapses such
1646+
// structurally equivalent frames. As a result, only one frame
1647+
// delivers to the target coordinate, and overdelivery is
1648+
// structurally ruled out.
1649+
//
1650+
// This test verifies that behavior holds as expected — and, when
1651+
// deduplication is disabled, confirms that overdelivery becomes
1652+
// observable.
1653+
#[test]
1654+
fn test_routing_deduplication_precludes_overdelivery() {
1655+
// Ensure the environment is clean — this test depends on a
1656+
// known configuration of deduplication behavior.
1657+
let var = "HYPERACTOR_SELECTION_DISABLE_ROUTING_FRAME_DEDUPLICATION";
1658+
assert!(
1659+
std::env::var_os(var).is_none(),
1660+
"env var `{}` should not be set prior to test",
1661+
var
1662+
);
1663+
let slice = test_slice();
1664+
1665+
// Construct a structurally duplicated selection.
1666+
//
1667+
// The union duplicates a singleton selection expression.
1668+
// Without deduplication, this would result in two logically
1669+
// identical frames targeting the same node — which should
1670+
// trigger an over-delivery panic in the simulation.
1671+
let a = range(0, range(0, range(0, true_())));
1672+
let sel = union(a.clone(), a.clone());
1673+
1674+
// Sanity check: with deduplication enabled (default), this
1675+
// selection does not cause overdelivery.
1676+
let result = std::panic::catch_unwind(|| {
1677+
let _ = collect_routed_paths(&sel, &slice);
1678+
});
1679+
assert!(result.is_ok(), "Unexpected panic due to overdelivery");
1680+
1681+
// Now explicitly disable deduplication.
1682+
std::env::set_var(var, "1");
1683+
1684+
// Expect overdelivery: the duplicated union arms will each
1685+
// produce a delivery to the same coordinate.
1686+
let result = std::panic::catch_unwind(|| {
1687+
let _ = collect_routed_paths(&sel, &slice);
1688+
});
1689+
1690+
// Clean up: restore environment to avoid affecting other
1691+
// tests.
1692+
std::env::remove_var(var);
1693+
1694+
assert!(
1695+
result.is_err(),
1696+
"Expected panic due to overdelivery, but no panic occurred"
1697+
);
1698+
}
16241699
}

ndslice/src/selection/test_utils.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ macro_rules! assert_round_trip {
5555
}};
5656
}
5757

58+
/// Determines whether routing frame deduplication is enabled.
59+
///
60+
/// By default, deduplication is enabled to reduce redundant routing
61+
/// steps and improve performance. However, correctness must not
62+
/// depend on deduplication.
63+
///
64+
/// This behavior can be disabled for debugging or testing purposes by
65+
/// setting the environment variable:
66+
/// ```ignore
67+
/// HYPERACTOR_SELECTION_DISABLE_ROUTING_FRAME_DEDUPLICATION = 1
68+
/// ```
69+
/// When disabled, all routing steps—including structurally redundant
70+
/// ones—will be visited, potentially causing re-entry into previously
71+
/// seen coordinates. This switch helps validate that correctness
72+
/// derives from the routing algebra itself—not from memoization or
73+
/// key-based filtering.
74+
fn allow_frame_dedup() -> bool {
75+
// Default: true (deduplication via memoization and normalization
76+
// is enabled unless explicitly disabled).
77+
std::env::var("HYPERACTOR_SELECTION_DISABLE_ROUTING_FRAME_DEDUPLICATION")
78+
.map_or(true, |val| val != "1")
79+
}
80+
5881
// == Testing (`collect_routed_paths` mesh simulation) ===
5982

6083
/// Message type used in the `collect_routed_paths` mesh routing
@@ -127,7 +150,12 @@ pub fn collect_routed_paths(selection: &Selection, slice: &Slice) -> RoutedPathT
127150
let mut visitor = |step: RoutingStep| {
128151
if let RoutingStep::Forward(next_frame) = step {
129152
let key = RoutingFrameKey::new(&next_frame);
130-
if seen.insert(key) {
153+
let should_insert = if allow_frame_dedup() {
154+
seen.insert(key) // true → not seen before
155+
} else {
156+
true // unconditionally insert
157+
};
158+
if should_insert {
131159
let next_rank = slice.location(&next_frame.here).unwrap();
132160
let parent_rank = *path.last().unwrap();
133161
predecessors
@@ -140,7 +168,12 @@ pub fn collect_routed_paths(selection: &Selection, slice: &Slice) -> RoutedPathT
140168

141169
match next_frame.action() {
142170
RoutingAction::Deliver => {
143-
delivered.insert(next_rank, next_path);
171+
if let Some(previous) = delivered.insert(next_rank, next_path.clone()) {
172+
panic!(
173+
"over-delivery detected: node {} delivered twice\nfirst: {:?}\nsecond: {:?}",
174+
next_rank, previous, next_path
175+
);
176+
}
144177
}
145178
RoutingAction::Forward => {
146179
pending.push_back(RoutedMessage::new(next_path, next_frame));

0 commit comments

Comments
 (0)