mastodon-sc / mastodon

Mastodon – a large-scale tracking and track-editing framework for large, multi-view images.
BSD 2-Clause "Simplified" License
66 stars 20 forks source link

Fix ConcurrentModificationException when showing the TrackScheme Hierarchy #200

Closed maarzt closed 1 year ago

maarzt commented 1 year ago

This PR fixes a ConcurrentModificationException, that occurs on some datasets, when the TrackScheme Hierarchy window is opened the first time. Here is the stack trace of this exception:

Exception in thread "PainterThread" java.util.ConcurrentModificationException
    at gnu.trove.impl.hash.THashPrimitiveIterator.nextIndex(THashPrimitiveIterator.java:83)
    at gnu.trove.impl.hash.THashPrimitiveIterator.moveToNextIndex(THashPrimitiveIterator.java:137)
    at gnu.trove.set.hash.TIntHashSet$TIntHashIterator.next(TIntHashSet.java:484)
    at org.mastodon.collection.ref.RefSetImp$Iter.next(RefSetImp.java:180)
    at org.mastodon.views.trackscheme.LexicographicalVertexOrder.sort(LexicographicalVertexOrder.java:62)
    at org.mastodon.views.trackscheme.LineageTreeLayoutImp.layout(LineageTreeLayoutImp.java:181)
    at org.mastodon.views.trackscheme.display.TrackSchemePanel.paint(TrackSchemePanel.java:374)
    at bdv.viewer.render.PainterThread.run(PainterThread.java:82)

The TrackScheme Hierarchy window is not rendered properly since the exception cancels the PainterThread: image

The window can be closed and reopened to workaround this problem.

Root cause

The problem is caused by multi threading. The branch graph is rebuild by the BranchGraphSynchronizer when the TrackScheme Heirarchy window is show. The branch graph rebuild also triggers a rebuild to the TrackSchemeGraph that is associated with the branch graph. The ConcurrentModificationException happens when the PainterThread renders the TrackSchemeGraph while it is modified.

Solution

The problem can be fixed by introducing a read-write-lock that prevents a simultaneous modification and rendering of the BranchGraph and it's associated TrackSchemeGraph.

  1. Add a ReentrantReadWriteLock to the ModelBranchGraph.
  2. Acquire the write-lock in the graphRebuilt method of the ModelBranchGraph.
  3. Also use this lock in the associated TrackSchemeGraph.
  4. ~Acquire the read-lock when rendering the TrackSchemeGraph.~ Update: The code for acquiring the read-lock during rendering is already in the TrackSchemePanel.
maarzt commented 1 year ago

This PR is ready to be reviewed & merged. (It provides a very straight forward solution to the concurrent modification problem. Modifications to the ModelBranchGraph are now protected by a ReadWriteLock in a similar fashion as this is done for the ModelGraph.)