Skip to content

Eventual verdict of Predefined and PredefinedWithoutParameterizedType flips between runs (now a ratchet wobble) #91

Closed

Description

@bnaudts

TestEventualRatchet (maddi-dist, the dogfood eventual ratchet) lost two types in one gate run, and kept them in every rerun at the same commits. They are now listed as a wobble (maddi-dist 295e418), which hides any future loss of them too. This issue is for finding the cause, so they can return to the expected set.

What happened

maddi slowTest gate 41393883829 on laser1 (2026-11-02) ran maddi d694ee7da, maddi-mod 5a58178 and maddi-dist bbaca1c:

The eventual ratchet moved: survivor set and/or keystone levels.
LOST (2):
    io.codelaser.maddi.cst.api.runtime.Predefined
    io.codelaser.maddi.cst.api.runtime.PredefinedWithoutParameterizedType

Reruns: no loss

The ratchet was run alone with --rerun and EC_RETRACT_DEBUG=1, at the same maddi-mod and maddi-dist:

maddi laser1 Mac
d694ee7da (the gate's) 2× 1×
a1697baa5 (before #166) 1×
af6512c45 (last green) 2× 1×

Every run kept both types. All the survivor sets are byte-identical (352: the 351 baseline plus the ImportComputer.ImportDetails wobble), and no run printed an ECRETRACT line. #166, the only change since the last green in what the dogfood analyses, is ruled out.

So the verdict flips at the same revision. The loss has only been seen inside the whole slowTest battery, where other buckets run in parallel on the same machine. That points at timing or ordering, like the ImportDetails and CompilationUnitPrinterImpl wobbles, but it is not traced.

Why it matters

  • Predefined is a large, central interface in cst-api. A run-to-run flip in its eventual verdict means the analysis result depends on scheduling. That is a bug in the analysis, whatever the ratchet does.
  • As a wobble, a genuine regression of these two types now passes the ratchet unseen.

What to do

  1. Reproduce: run the dogfood eventual analysis repeatedly under load (alongside other heavy buckets, or with a different parallelism) with EC_RETRACT_DEBUG=1, until Predefined is lost, and capture the ECRETRACT broken-list of that run.
  2. Find what the eventual verdict depends on that is order-dependent: the drift round in docs/design/eventual-info-hierarchy.md, and the verification-pass residue boundary that explains ImportDetails.
  3. Make the verdict deterministic, then move both types back from dogfood/eventual-survivor-wobble.txt to dogfood/expected-eventual-survivors.txt in maddi-dist.

Done when the two types are back in the expected set and the ratchet stays green across several full slowTest gates.

Activity

  1. added this to the 0.9.7 milestone on Nov 2, 2026
  2. bnaudts commented on Nov 4, 2026

    @bnaudts
    Author

    Tried to reproduce it by perturbing thread timing on a quiet laser1 (maddi-mod 737913b, maddi-dist 88e2ce4). The dogfood eventual ratchet ran 8 times:

    runs settings
    5 PARALLEL = 8, 3, 2, 5, 1
    1 NOSUPERWAIT=1 (the #82 supertype fix switched off)
    2 CBSWEEP=1 at 8 and 3 threads

    Separately, every run fails the ratchet on 4 survivors gained, none lost:

    io.codelaser.maddi.cst.impl.element.RecordPatternImpl.$1
    io.codelaser.maddi.cst.impl.type.ParameterizedTypeImpl.$11
    io.codelaser.maddi.cst.impl.type.TypeParameterImpl.$0
    io.codelaser.maddi.cst.impl.variable.FieldReferenceImpl.$0
    

    They appear with the #82 supertype fix on and off, and with CBSWEEP on and off, so they come from another change on devel. The expected set needs updating at the next gate.

  3. bnaudts commented on Nov 4, 2026

    @bnaudts
    Author

    Reproduced without load. PARSHUFFLE=<seed> (97007ab, a diagnostic) submits each parallel pass's elements in a seeded random order. Dogfood eventual ratchet, laser1, ungated, 6 seeds:

    seeds Predefined + PredefinedWithoutParameterizedType other
    2, 4, 6 kept —
    1, 3 lost —
    5 lost also loses OutputElement + 5 output types; 7 keystones (Element, Expression, Info, FieldInfo, ParameterInfo, TypeInfo, TypeInfoImpl) drop to @FinalFields after the mark

    So the eventual verdicts depend on the order of the parallel pass, not only on load.

    Mechanism (found on guava, #82): the eventual analysis keeps a ledger of the types each verdict leaned on. A type is recorded only if it had no verdict of its own at that moment. While that ledger is built inside the parallel loop, what gets recorded depends on thread timing, and the contraction then retracts different sets.

    Fix candidate: CBSWEEP (#82). It moves the type analyzers into the sequential second type pass and applies cycle breaking only in a sequential sweep. A first dogfood run with it was deterministic across seeds, but lost 44 survivors. The cause was not retraction: it ran out of iterations (120) before the final stage converged, because one sweep round per pass made every link of a verdict chain cost a whole round. 3040f72 repeats the sweep in place until it changes nothing; the dogfood run on it is going now.

  4. added a commit that references this issue on Nov 4, 2026
  5. bnaudts commented on Nov 5, 2026

    @bnaudts
    Author

    Fixed in devel: CBSWEEP is now the default (c3ba74b). The eventual assumption ledger is no longer written inside the parallel loop. In maddi-all's measurement with CBSWEEP, the dogfood keystone drops went from 11 to 1, and maddi 8771dabce (now in maddi) fixes the remaining one, Element.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions