Chart.combine sometimes results in "Collection was modified; enumeration operation may not execute."
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 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.
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.
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.
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.
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.
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.
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