-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Array#map
is vulnerable to concurrent mutation
#15161
Comments
Actually modifying an array while iterating it has very few if any actual use cases (or the issues would have shown up a lot earlier). Could it be considered to instead simply forbid modifying the array during iteration? I feel it would be a shame to pay a performance price for something that isn't really how people normally use the thing. One way to implement that would be to use an rw_lock, though perhaps with a variant that raises if it fails to grab the write lock immediately. Upside: Would cost a lot less performance as it is only done once per iteration Thoughts? |
This is definitely worth considering. Such a protection must cover all operation that can potentially invalidate the buffer range. That's most mutating methods on |
As outlined by @BlobCodes, Java throws a ConcurrentModificationException exception when it detects a concurrent iterate/mutate situation (no guarantee: it's a best effort). |
This issue has been mentioned on Crystal Forum. There might be relevant details there: https://forum.crystal-lang.org/t/safety-guarantees-of-stdlib-data-structures/7364/17 |
The mention of |
Golang detects concurrent modification on |
Considering that both Go and Java have a protection mechanism to detect concurrent modifications in generic data structures, the trade-off seems to be worthwile. A major benefit is of course that it also triggers at least for some types of parallel modifications. So it would have a relevant effect thread safety, to error gracefully instead of ending up with corrupted data or a segfault. |
Digging a bit into the mechanisms in Go and Java it turns out their implementations - and apparently purposes - are quite different. Go uses a boolean flag which indicates whether a modification is currently happening. It's set via XOR which according to the doc comment should make it likely that if two fibers both try to mutate simultaneously, both notice the collision. This protects the integrity of the data structure itself, but not any assumptions about structural stability as would be necessary for the Java on the other hand uses a variable I suppose this could also be used to detect simultaneous modifications, at least after the fact: Before a method does a structural modification, it takes a snapshot of the current It might be possible to combine both approaches into a single variable, a modification counter where one bit indicates whether someone is currently writing. 🤔 |
Thanks for the description of approaches in Go and Java. They sound nice. I think the approaches that detect concurrent modification after the fact are just fine. |
Array#map(&)
yields each element to transform it in the block. If the block performs mutating operations on the array, it can lead to invalid memory access or corrupted data.The same applies to
Array#map_with_index
, and I mean both when I refer to#map
.This is discussed as an example in https://forum.crystal-lang.org/t/safety-guarantees-of-stdlib-data-structures/7364
A simple reproduction of an example with data corruption:
The cause is that
Array#map
expectssize
to be static during its runtime. While this is generally a reasonable expectation, it's impossible to exclude that the block mutates the array's size.Array#map
is an optimized override. The base implementation ofEnumerable#map
does not have this bug because it does not assume any size. It grows the buffer on demand.The easiest solution is to drop the implementation of
Array#map
in order to fall back toEnumerable#map
. We can still optimize a bit by using the current size as size hint because usually it's gonna be stable.The text was updated successfully, but these errors were encountered: