Skip to content

Commit f1bc608

Browse files
committed
fix(linter): fix flaky import/no_cycle test (#14328)
## Summary Fixes the flaky `import/no_cycle` test that was failing 2/10 times on Linux CI. ## Root Cause The module graph is populated via parallel insertion (`par_drain` in `runtime.rs`), which causes non-deterministic insertion order into `FxHashMap`. Since hashmap iteration order depends on insertion order, this leads to: 1. Different iteration orders across test runs 2. Different graph traversal paths in the cycle detector 3. The `traversed` set preventing revisiting nodes means cycles can be found or missed depending on which path is explored first ## Solution Sort the hashmap entries by key before iterating in `module_graph_visitor.rs` to ensure deterministic traversal order regardless of parallel insertion timing. ## Test Plan - [x] `cargo test -p oxc_linter no_cycle` passes - [x] Code formatted with `just fmt` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent a2914fe commit f1bc608

File tree

1 file changed

+19
-3
lines changed

1 file changed

+19
-3
lines changed

crates/oxc_linter/src/module_graph_visitor.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::{marker::PhantomData, path::PathBuf, sync::Arc};
1+
use std::{
2+
marker::PhantomData,
3+
path::PathBuf,
4+
sync::{Arc, Weak},
5+
};
26

37
use rustc_hash::FxHashSet;
48

@@ -189,7 +193,19 @@ impl ModuleGraphVisitor {
189193
enter: &mut EnterMod,
190194
leave: &mut LeaveMod,
191195
) -> VisitFoldWhile<T> {
192-
for (key, weak_module_record) in module_record.loaded_modules().iter() {
196+
// Sort entries to ensure deterministic iteration order.
197+
// The module graph is populated via parallel insertion (par_drain in runtime.rs),
198+
// which causes non-deterministic insertion order into FxHashMap.
199+
// Different iteration orders can cause cycle detection to find or miss cycles
200+
// depending on which path reaches a node first (due to the `traversed` set).
201+
let mut entries: Vec<_> = module_record
202+
.loaded_modules()
203+
.iter()
204+
.map(|(k, v)| (k.clone(), Weak::clone(v)))
205+
.collect();
206+
entries.sort_unstable_by(|a, b| a.0.cmp(&b.0));
207+
208+
for (key, weak_module_record) in entries {
193209
if self.depth > self.max_depth {
194210
return VisitFoldWhile::Stop(accumulator.into_inner());
195211
}
@@ -202,7 +218,7 @@ impl ModuleGraphVisitor {
202218
continue;
203219
}
204220

205-
let pair = (key, &loaded_module_record);
221+
let pair = (&key, &loaded_module_record);
206222

207223
if !filter(pair, module_record) {
208224
continue;

0 commit comments

Comments
 (0)