Plotly.NET icon indicating copy to clipboard operation
Plotly.NET copied to clipboard

Chart.combine sometimes results in "Collection was modified; enumeration operation may not execute."

Open nbevans opened this issue 3 years ago • 7 comments

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.KeyCollection.Enumerator.MoveNext()
   at <StartupCode$DynamicObj-FSC472>[email protected](IEnumerable`1& next)
   at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase`1.MoveNextImpl()
   at Microsoft.FSharp.Collections.SeqModule.ToList[T](IEnumerable`1 source)
   at DynamicObj.DynamicObj.GetProperties(Boolean includeInstanceProperties)
   at DynamicObj.DynObj.combine(DynamicObj first, DynamicObj second)
   at DynamicObj.DynObj.combine(DynamicObj first, DynamicObj second)
   at DynamicObj.DynObj.combine(DynamicObj first, DynamicObj second)
   at Plotly.NET.GenericChart.combineLayouts@243(Layout first, Layout second)
   at [email protected](GenericChart acc, GenericChart elem)

The sequences of data I'm feeding it are from immutable lists; so I don't think it's a problem with the way I'm calling Plotly.NET.

I can try again (with the same data) and usually it is successful. Seems to be a sporadic issue. Very odd.

nbevans avatar Oct 17 '22 15:10 nbevans

@nbevans is this called from C# or F#? Could you provide a reproducible example with dummy data? Looking at the stack trace this looks to me like an issue with the underlying DynamicObj library, but I cannot be sure until I can try to debug this issue myself.

kMutagene avatar Oct 18 '22 08:10 kMutagene

Looking at this stack overflow post, it might also be a problem on how exactly you access the data in the collection, but we cannot be sure without seeing the code.

kMutagene avatar Oct 18 '22 08:10 kMutagene


let result =
    [
        (("2020-06-09", 2), ("2020-06-09", 2))
        (("2020-08-04", 1), ("2020-08-04", 3))
        (("2020-09-04", 1), ("2020-09-04", 4))
        (("2020-10-14", 2), ("2020-10-14", 6))
        (("2020-10-16", 2), ("2020-10-16", 8))
        (("2020-12-01", 1), ("2020-12-01", 9))
        (("2021-01-11", 1), ("2021-01-11", 10))
        (("2021-02-17", 2), ("2021-02-17", 12))
        (("2021-07-06", 2), ("2021-07-06", 14))
        (("2021-09-21", 1), ("2021-09-21", 15))
        (("2022-10-13", 2), ("2022-10-13", 17))
    ]
let f () =
    [
        Chart.Area(result |> Seq.map snd, Name = "Total registrations")
        |> Chart.withLineStyle (Color = Color.fromString "RoyalBlue")
        Chart.Line(result |> Seq.map fst, Name = "New registrations")
        |> Chart.withLineStyle (Color = Color.fromString "ForestGreen")
    ]
    |> Chart.combine
    |> Chart.withLayoutStyle (HoverMode = StyleParam.HoverMode.XUnified)
    |> Chart.withXAxisStyle (TitleText = "Date", AxisType = StyleParam.AxisType.Date)
    |> Chart.withYAxisStyle (TitleText = "Count")

for n in Seq.init 1000 id do f () |> ignore

This seems sufficient to provoke the issue to occur after a run or two.

nbevans avatar Oct 19 '22 08:10 nbevans

Thanks! In what context is this run? using a .NET interactive notebook and .NET 6.0.400 I can run this hundreds of times and still don't encounter this issue. The same is true for a .fsx script.

kMutagene avatar Oct 19 '22 08:10 kMutagene

Yeah I'm struggling to repro it myself via FSI. It must be something caused by my actual project's environment (NetFx / NetStandard2.0 / FSharp.Core 4.7.2)

I've got a feeling they relaxed that enumeration "collection was modified" guard in newer .NET's.

Or maybe some changes in FSCore such as https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1099-list-collector.md had the effect of mitigating this scenario from occurring entirely.

nbevans avatar Oct 19 '22 09:10 nbevans

https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.keycollection.enumerator.movenext?view=net-6.0

Perhaps this:

.NET Core 3.0+ only: The only mutating methods which do not invalidate enumerators are Remove and Clear.

nbevans avatar Oct 19 '22 09:10 nbevans

https://github.com/CSBiology/DynamicObj/blob/main/src/DynamicObj/DynObj.fs#L53

Interesting code comment on this line "Consider deep-copy of first" - seems to suggest DynamicObj author was aware of potential risks of the enumeration and recursive mutations. Not 100% sure why it doesn't crop up on newer .NETs yet though

nbevans avatar Oct 19 '22 10:10 nbevans