Skip to content
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

Fix ConcurrentModificationException in TestMethodController #6689

Merged
merged 1 commit into from
Nov 13, 2023

Conversation

lkishalmi
Copy link
Contributor

Just happened to bump into this CME, while working on Jenkinsfile support. It seems this has been reported as #5148.

It seems the CME happens as the methods list is a live one passed over as parameter, so the collection can be changed during the time of methods.forEach(tm -> removed.remove(tm)). The solution would be to copy that collection before do the remove work. Interestingly we do have a copy of right after that call, so a simple reorder would solve the issue. I've just replaced the forEach call with keySet().removeAll as I think that reads better.

Otherwise the change is trivial. If it does not make into NB20, then I'd rebase this for master.

@lkishalmi lkishalmi added Java [ci] enable extra Java tests (java.completion, java.source.base, java.hints, refactoring.java, form) tests labels Nov 13, 2023
@lkishalmi lkishalmi added this to the NB20 milestone Nov 13, 2023
@mbien
Copy link
Member

mbien commented Nov 13, 2023

the root of this issue is likely that modifiable collections are shared over the document properties.

I believe the risk of CME is still there, it just moved from the forEach method to the copy constructor of the HashSet/HashMap? It should still fail if the concurrent modification is noticed while it is iterating to build the copy. (CMEs are not guaranteed to be thrown in many cases, its more a best-effort manner for bug detection, it often simply compares col sizes)

edit: i haven't checked the access pattern yet, I don't know from where else the collections are accessed, one red flag is that the empty collection is added to the properties and modified afterwards, i suppose someone could listen on property changes and react while the method is still running.

edit2: usages of the keys are:

Copy link
Member

@mbien mbien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible way out is the copy on write pattern:

for write:

  • defensive copy after props.get
  • modify defensive copy
  • put update with Collecitons.unmodifiableFoo(defensiveCopy) into props

for read:

  • props.get and read remains simple

or synchronized collections, but the client has to know that it is synchonized and use the lock. JDK has also copy-on-write lists and sets but no map I believe.

edit: lock-free copy-on-write works only reliably if there is only one writer, otherwise writes can be reset by concurrent writers.

@lkishalmi
Copy link
Contributor Author

@mbien Thanks for the review! It made me look what's upper on the road and found the one and only call to this method in ComputeTestMethodsImpl and indeed the CME has been built in the system. Here I've just added a defensive copy, this time I hope in the right place. Probably an extra unmodifiable wrapper would have been nice, though this late in the release phase I do not wish extra exceptions popping up due to that.

Copy link
Member

@mbien mbien left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might work, thanks!

We should revisit this for NB21 and either make the shared collections immutable, or find some other simple way of access control.
(at the very least it has to be documented in-code so that nobody optimizes the defensive copies away again :))

@neilcsmith-net
Copy link
Member

👍

We should revisit this for NB21 and either make the shared collections immutable

*.copyOf(..) ftw!

@neilcsmith-net neilcsmith-net merged commit 2bffacd into apache:delivery Nov 13, 2023
@mbien
Copy link
Member

mbien commented Nov 13, 2023

I always forget that the copyOf methods exist. I should use them more.

@neilcsmith-net
Copy link
Member

They have the added benefit of being a no-op in cases where you stack defensive copies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Java [ci] enable extra Java tests (java.completion, java.source.base, java.hints, refactoring.java, form) tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants