-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.Skip allows a Collection to be modified during enumeration #40415
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
|
When using |
It doesn't look to use the IEnumerable but use IList indexing (hence modified enumerator not triggered). However, it should probably use runtime/src/libraries/System.Linq/src/System/Linq/Skip.SpeedOpt.cs Lines 11 to 13 in 6072e4d
Uses both runtime/src/libraries/System.Linq/src/System/Linq/Partition.SpeedOpt.cs Lines 177 to 180 in 6072e4d
|
Not a bug in itself, since it's iterating over the "original" items. Since it appears to be using indices directly, it's likely snapshotting the range. @benaadams - possibly, although that's still not going to throw in all the same cases (ie, |
It wouldn't throw at all; however throwing is a peculiarity of some collections not guaranteed by IEnumerable. Looping effectively forever is more of an issue? |
True, yes. |
Tagging subscribers to this area: @eiriktsarpalis |
No, there shouldn't be a difference functionally between: List<T> list = ...;
IEnumerable<T> e = list.SomeLinqOps();
list.Add(...)/Remove(...)/etc.;
foreach (T t in e) { ... } and List<T> list = ...;
list.Add(...)/Remove(...)/etc.;
IEnumerable<T> e = list.SomeLinqOps();
foreach (T t in e) { ... } Snapshotting of anything to do with the contents of the list shouldn't happen until the enumerable is actually enumerated. |
We could perhaps improve it so that the list count is snapshotted on |
I tried prototyping snapshotting logic for ListPartition however I'm not particularly satisfied with the result. Due to the current design of the internal The question then is whether we consider the potential infinite loop serious enough to make such an inclusion. |
I'm inclined to close this without resolution, but I'd like to confirm a couple understandings I have on it.
|
This issue has been marked |
I believe the root cause of this started from a performance optimization and now exposed this issue. I understand that performance is important and this is a bit of an edge case. In my case it was an accident during testing, while writing unit tests as I was creating a lot of test data. With Keeping the performance penalty in mind, can there be a simple guard in a debug configuration? It's really terrible to run into this. Remind me of classic asp and forgetting the |
There are no scenarios where this is reasonable, even without the infinite loop bug. Modifying a collection during an enumeration is always user error, and should be fixed by one or more different mechanisms (yielding an additional item, building a list to concatenate on the end...) |
I should clarify that the workaround I had proposed does not guard against mutation in general, it simply attaches an upper bound to specifically prevent the type of blow-up encountered in the original example. The following would still be legal: var example = new List<string>() { "test0","test1" };
var i = 0;
foreach (var item in example.Skip(1))
{
example.RemoveAt(^1);
} I suspect a "correct" fix would involve removing the |
Closing as by-design, combining mutation with LINQ is arguably a corner case (or rather anti-pattern) that can result in surprising behavior in the presence of performance optimizations. I don't think we should be reverting optimizations on lists for that reason. |
Description
I flipped a variable and found out that .Skip allows the IEnumerable to be modified during enumeration which caused an infinite loop. Related to a similar issue that was moved in [dotnet/corefx] (#32278).
Tested without LINQ, and the exception is thrown that you cannot modify a IEnumerable in progress.
Tried with .OrderBy() and it allowed the code to run but did not add new entries to the end of the IEnumerable and would only run twice, which could be a separate bug.
I have not tried with a LINQ/Lambda only statement as the first time this happened it locked a 24gb Ryzen machine.
Configuration
.Net 3.1 ,Windows 10 x64
Regression?
Previously any modification of the IEnumerable while enumerating caused an exception.
Other information
An optimization caused an issue with a similar bug report for 2.1 dealing with infinite loops and IEnumerable [dotnet/corefx] (#32278). It was decided to roll back that merge request to fix the issue.
The text was updated successfully, but these errors were encountered: