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
- Treat this as a library invariant bug: ensure clocks passed to Diff were only built via `VectorClock` constructors, `Merge`, and `Next`.
- Check deserialization code for keys that are dropped asymmetrically between the two clocks.
- 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
- Build clocks only via VectorClock constructors, Merge and Next.
- Avoid custom serialization; if needed, round-trip test it against Diff.
- Never mutate clock internals with reflection or unsafe casts.
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
- 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/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)