Skip to content

TOMEE-4699 Fix ConcurrentModificationException in ThreadContext constructor - #2939

Open
JorrenH wants to merge 4 commits into
apache:mainfrom
JorrenH:TOMEE-4699
Open

TOMEE-4699 Fix ConcurrentModificationException in ThreadContext constructor#2939
JorrenH wants to merge 4 commits into
apache:mainfrom
JorrenH:TOMEE-4699

Conversation

@JorrenH

@JorrenH JorrenH commented Sep 8, 2026

Copy link
Copy Markdown

See Jira ticket for full context.

In summary:

  • HashMap#putAll on a synchronizedMap is not thread-safe. It will access .entrySet().iterator() internally, allowing it to cause a ConcurrentModificationException.
  • This PR simply synchronizes on the data so the iterator call remains safe.

Note: ConcurrentHashMap does not work here because it does not allow null values.

@otbutz

otbutz commented Sep 8, 2026

Copy link
Copy Markdown
  • HashMap#putAll on a synchronizedMap is not thread-safe. It will access .entrySet().iterator() internally, allowing it to cause a ConcurrentModificationException.

To be precise, there are two mutexes involved: putAll only synchronises on this.data. However, accessing that.data via an iterator happens without synchronisation, which violates the contract of Collections.synchronizedMap().

@otbutz otbutz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I see no reason to copy the whole map?

Copying a ThreadContext iterates the source map. When the thread owning that
context updates it at the same time the copy fails with a
ConcurrentModificationException, which is what made tasks submitted to a managed
executor disappear.

The test copies a context on one thread while another updates it. It fails
within a fraction of a second without the synchronized block in the copy
constructor, and passes with it.

@rzo1 rzo1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, thanks for tracking this down.

I pushed a reproducer to the branch: ThreadContextCopyRaceTest copies a ThreadContext on one thread while another updates it. It fails within about half a second without the synchronized block and passes with it, so it should be a lot easier to work with than reproducing under load with a debugger attached. Feel free to change or drop it if you would rather keep the PR minimal.

I left one non-blocking note on the dataToString thread about toString() reading the size outside the lock.

@JorrenH

JorrenH commented Sep 10, 2026

Copy link
Copy Markdown
Author

@rzo1 Good to have a quick reproducer, thanks! I updated toString() such that the size is consistent with the entries since it's a quick win. Did pull the whole line into dataToString to avoid a change in the format (data=(2)=.. vs data(2)=..)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants