louthy/language-ext · error · InvalidOperationException
diff broken
Error message
diff broken
What it means
Same `diff broken` invariant check, in the `VectorClock<OrdA, NumB, A, B>` variant's `diffOne`: a (None, Some) entry pair or unrecognized case when diffing the B-typed entries throws `InvalidOperationException("diff broken")`. The B-clock diff can never legitimately see a key absent in the receiver but present in the argument.
Solutions
- Only obtain clocks through public constructors and `Merge`/`Next`; never mutate internals.
- Ensure both clocks passed to Diff share the same key domain (same origin/lineage).
- If reachable via valid public API, file a bug with LanguageExt including repro steps.
Defensive patterns
Strategy: try-catch
Validate before calling
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 from common ancestor */ } Prevention
- Keep both clocks in the same merge lineage (same key domain).
- Do not mix clocks from independent origins in one Diff call.
- Rely only on public Merge/Next/constructors.
When it happens
Trigger: Calling `Diff` on a two-type-parameter VectorClock where the argument clock contains a key the receiver lacks, or an option holds a non-B/non-null case — only possible with inconsistently built clocks.
Common situations: Mixing clocks from different merge lineages; custom merge/serialization code that mutates one clock but not the other; bugs in user code that manipulates the underlying map via reflection or unsafe casts.
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
- diff broken
- Refs can only be written to from within a `sync` transaction
- Refs can only commute from within a transaction
- Transaction not running
- List is empty
AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15).
Data as JSON: /api/errors/706ae770cfa3f049.
Report an issue: GitHub.
Appendix: source
Thrown at LanguageExt.Core/Concurrency/VectorClock/VectorClock.OrdA.NumB.A.B.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<OrdA, NumB, A, B>> diff(VectorClock<OrdA, NumB, A, B> vc1, VectorClock<OrdA, NumB, A, B> vc2)
{
return vc1 == vc2
? Some(Empty)
: causes(vc2, vc1)
? Some(combine(diffOne, vc1, vc2))
: None;
static Option<B> diffOne(A _, Option<B> ox, Option<B> oy) =>
(ox.Case, oy.Case) switch
{
(null, null) => None,
(B x, null) => Some(x),
(null, B) => throw new InvalidOperationException("diff broken"),
(B x, B y) => equals<NumB, B>(x, y) ? None : Some(x),
_ => None
};
}
bool? valid;
public bool Valid
{
get
{
if (valid.HasValue) return valid.Value;
var keys = Entries.Map(e => e.Item1);
var sorted = keys.Sort<OrdA, A>() == keys;
var distinct = Entries.Distinct<EqTuple2<OrdA, NumB, A, B>, (A, B)>().Count == Entries.Count;
valid = sorted && distinct;
return valid.Value;
}
}View on GitHub (pinned to 2f0e362824)