Skip to content

Commit 29f786b

Browse files
proggeramlugRalph Küpper
andauthored
test(runtime): let the dispatch-IC generation retry actually fire (#7365) (#7822)
Co-authored-by: Ralph Küpper <ralph@skelpo.com>
1 parent 4a49da4 commit 29f786b

2 files changed

Lines changed: 47 additions & 17 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
**One of #7365's two named flaky tests is fixed**: `obj_dispatch_ic_tests` goes from **10 of 12** isolated runs failing to **0 of 20**.
2+
3+
`with_stable_gen` exists because dispatch-IC entries are keyed on `VTABLE_GEN`, and any class registration anywhere retires the cache — including `a_class_registration_invalidates_every_entry` in the *same module*, which calls `test_bump_vtable_generation()` on purpose. So an insert/lookup pair can straddle a bump and miss for a reason that has nothing to do with what is being asserted. The helper was written to retry in exactly that case.
4+
5+
**The retry could never fire.** `body` asserted internally, so a straddled pair panicked on the *setup* assertion ("the entry we just inserted must be findable") before the loop got to re-check the generation. The 64-retry budget was never spent.
6+
7+
`body` now returns whether its observations were valid: `false` means "a bump landed mid-pair, nothing was learned", which is a retry rather than a failure. The assertions the tests exist for — a different name at the same address must miss, a different class id must miss, an over-long name must not alias a truncated key, a prefix must not hit — stay assertions.
8+
9+
**Why this matters beyond one test, and why it looked so strange.** The failure rate *inverted with scope*: running only this module put its five tests — including the deliberate bumper — on threads together and failed **10 of 12**, while the full 2000-test suite spread them apart and failed about **1 in 6**. So the standard triage move, re-running just the failing test, made an intermittent test look reliably broken, and running the suite made it look nearly fine. Both readings were wrong, and #7365 is largely a catalogue of exactly that confusion.
10+
11+
Measured after the fix: **0/20** isolated runs fail. The full suite still fails about 1 in 10, but on a different test — `gc::tests::root_words::bare_address_in_global_root_survives_a_real_collection`, a sibling of the `bare_address_in_shadow_slot_…` case already measured at 2/10 on clean `main`. That family is untouched here and remains #7365's other half; it is a GC-timing intermittency rather than a test-isolation one, so it needs a different fix.

crates/perry-runtime/src/object/class_registry/dispatch.rs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -526,18 +526,34 @@ mod obj_dispatch_ic_tests {
526526

527527
const CID: u32 = 61_001;
528528

529-
/// Run `body` on a stable vtable generation.
529+
/// Run `body` on a stable vtable generation, retrying if it straddled a bump.
530530
///
531531
/// Entries are keyed on `VTABLE_GEN`, and the whole point of that key is
532-
/// that ANY class registration anywhere retires the cache. Sibling tests in
533-
/// this crate register classes concurrently, so an insert/lookup pair can
534-
/// straddle a bump and miss for a reason that has nothing to do with what
535-
/// is being asserted. Retry until the pair runs inside one generation.
536-
fn with_stable_gen(body: &dyn Fn()) {
532+
/// that ANY class registration anywhere retires the cache. Sibling tests
533+
/// register classes concurrently — `a_class_registration_invalidates_every_entry`
534+
/// in THIS module calls `test_bump_vtable_generation()` on purpose — so an
535+
/// insert/lookup pair can straddle a bump and miss for a reason that has
536+
/// nothing to do with what is being asserted.
537+
///
538+
/// #7365: the retry existed but could not fire. `body` asserted internally,
539+
/// so a straddled pair **panicked on the setup assertion** before the loop
540+
/// got to re-check the generation, and the retry budget was never spent.
541+
/// That is why the failure rate INVERTED with scope: running just this
542+
/// module (`--lib obj_dispatch_ic_tests`) put its five tests — including
543+
/// the deliberate bumper — on threads together and failed 10 of 12 runs,
544+
/// while the full 2000-test suite spread them out and failed about 1 in 6.
545+
/// A filtered re-run, the standard triage move, therefore made an
546+
/// intermittent test look reliably broken.
547+
///
548+
/// So `body` now REPORTS whether its observations were valid instead of
549+
/// asserting them: `false` means "a bump landed mid-pair, nothing was
550+
/// learned", which is a retry rather than a failure. The assertions the
551+
/// tests actually exist for stay assertions.
552+
fn with_stable_gen(body: &dyn Fn() -> bool) {
537553
for _ in 0..64 {
538554
let before = VTABLE_GEN.load(Ordering::Acquire);
539-
body();
540-
if VTABLE_GEN.load(Ordering::Acquire) == before {
555+
let observed = body();
556+
if observed && VTABLE_GEN.load(Ordering::Acquire) == before {
541557
return;
542558
}
543559
}
@@ -557,30 +573,31 @@ mod obj_dispatch_ic_tests {
557573
// different name through the SAME backing storage.
558574
let mut scratch = *b"area\0\0\0\0";
559575
obj_dispatch_ic_insert(CID, &scratch[..4], 0xAAAA, 1, false, false);
560-
assert_eq!(
561-
obj_dispatch_ic_lookup(CID, &scratch[..4]),
562-
Some((0xAAAA, 1, false, false)),
563-
"the entry we just inserted must be findable"
564-
);
576+
// Setup, not the subject: a miss here means a sibling bumped the
577+
// generation between insert and lookup. Report it and retry.
578+
if obj_dispatch_ic_lookup(CID, &scratch[..4]) != Some((0xAAAA, 1, false, false)) {
579+
return false;
580+
}
565581

566582
scratch[..4].copy_from_slice(b"perim"[..4].try_into().unwrap());
567583
assert_eq!(
568584
obj_dispatch_ic_lookup(CID, &scratch[..4]),
569585
None,
570586
"a different name at the same address must MISS"
571587
);
588+
true
572589
});
573590
}
574591

575592
#[test]
576593
fn a_hit_requires_the_matching_class_id() {
577594
with_stable_gen(&|| {
578595
obj_dispatch_ic_insert(CID, b"describe", 0xBBBB, 1, false, false);
579-
assert_eq!(
580-
obj_dispatch_ic_lookup(CID, b"describe"),
581-
Some((0xBBBB, 1, false, false))
582-
);
596+
if obj_dispatch_ic_lookup(CID, b"describe") != Some((0xBBBB, 1, false, false)) {
597+
return false;
598+
}
583599
assert_eq!(obj_dispatch_ic_lookup(CID + 1, b"describe"), None);
600+
true
584601
});
585602
}
586603

@@ -605,6 +622,7 @@ mod obj_dispatch_ic_tests {
605622
"an over-long name must fall through to the tower, not alias a \
606623
truncated key"
607624
);
625+
true
608626
});
609627
}
610628

@@ -615,6 +633,7 @@ mod obj_dispatch_ic_tests {
615633
with_stable_gen(&|| {
616634
obj_dispatch_ic_insert(CID, b"describe", 0xEEEE, 1, false, false);
617635
assert_eq!(obj_dispatch_ic_lookup(CID, b"describ"), None);
636+
true
618637
});
619638
}
620639
}

0 commit comments

Comments
 (0)