louthy/language-ext · error · InvalidOperationException

diff broken

Error message

diff broken

What it means

`VectorClock<A>.diff` compares two vector clocks entry-by-entry via `diffOne`. For each key it expects the pair of clock entries to be (None,None), (Some,None), or (Some,Some); a (None,Some) pair — a key present in the operand clock but absent in the receiver — is structurally impossible for a valid diff, so the library throws `InvalidOperationException("diff broken")` to signal a broken internal invariant.

Solutions

  1. Treat this as a library invariant bug: ensure clocks passed to Diff were only built via `VectorClock` constructors, `Merge`, and `Next`.
  2. Check deserialization code for keys that are dropped asymmetrically between the two clocks.
  3. If reproducible with valid clocks, file a bug with the LanguageExt maintainers including the two clock states.
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure clocks share lineage before diffing
if (!clockA.Valid || !clockB.Valid) throw new InvalidOperationException("invalid vector clocks");

Try / catch

try { var d = a.Diff(b); }
catch (InvalidOperationException ex) when (ex.Message == "diff broken") { /* rebuild clocks from source events */ }

Prevention

When it happens

Trigger: An entry exists in the second (argument) clock but not in the first (receiver) clock when `Diff` is called — which only happens if a VectorClock was constructed/mutated inconsistently, or with clocks from different generations where the invariant is broken.

Common situations: Custom serialization round-trips that drop entries asymmetrically; hand-built or mutated clocks (reflection, partial deserialization) that violate the merge invariant; comparing clocks produced by different, incompatible merge histories.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15). Data as JSON: /api/errors/31f91457a3b4aeef. Report an issue: GitHub.

Appendix: source

Thrown at LanguageExt.Core/Concurrency/VectorClock/VectorClock.A.cs:331

    /// <summary>
    /// If vc2 causes vc1, compute the smallest vc3
    /// </summary>
    /// <remarks>Note that the /first/ parameter is the newer vector clock.</remarks>
    public static Option<VectorClock<A>> diff(VectorClock<A> vc1, VectorClock<A> vc2)
    {
        return vc1 == vc2
                   ? Some(Empty)
                   : causes(vc2, vc1)
                       ? Some(combine(diffOne, vc1, vc2))
                       : None;

        static Option<long> diffOne(A _, Option<long> ox, Option<long> oy) =>
            (ox.Case, oy.Case) switch
            {
                (null, null)     => None,
                (long x, null)   => Some(x),
                (null, long)     => throw new InvalidOperationException("diff broken"),
                (long x, long y) => x == y ? None : Some(x),
                _                => throw new InvalidOperationException("diff broken")
            };
    }

    bool? valid;
    public bool Valid 
    {
        get
        {
            if (valid.HasValue) return valid.Value;
            var keys   = Entries.Map(e => e.Item1);
            var sorted = keys.Sort<OrdDefault<A>, A>() == keys;
            var distinct = Entries.Distinct<EqTuple2<OrdDefault<A>, TLong, A, long>, (A, long)>().Count ==
                           Entries.Count;
            valid = sorted && distinct;
            return valid.Value;
        }

View on GitHub (pinned to 2f0e362824)