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

Multithreading issue with creation or removal of objects #744

Closed
MichaelSNelson opened this issue Jun 6, 2021 · 0 comments · Fixed by #750
Closed

Multithreading issue with creation or removal of objects #744

MichaelSNelson opened this issue Jun 6, 2021 · 0 comments · Fixed by #750
Assignees
Labels
Milestone

Comments

@MichaelSNelson
Copy link

MichaelSNelson commented Jun 6, 2021

Copy and paste from: https://forum.image.sc/t/semi-random-script-errors-when-adding-removing-objects/53617

I am using a script to demonstrate both removing and adding objects, ideally for new coders (dun dun dunnn). The setting is a small rectangular annotation with cells generated inside of it, which is also divided into tumor/stroma annotations.
image

The purpose of the script is to remove the cells in one area, run an analysis that adds measurements based only on the cells in the second area, and then restore the cells that were removed.

//Load the LuCa object data before running!
resolveHierarchy() //let's make sure all of the cells are child objects of their annotations!
tumorAnnos = getAnnotationObjects().findAll{it.getPathClass() == getPathClass("Tumor")}
tumorCells = getCellObjects().findAll{it.getParent().getPathClass() == getPathClass("Tumor")}

//Remove the tumor annotations and their cells
removeObjects(tumorAnnos,false)
removeObjects(tumorCells,false)
//Analyze->Spatial analysis->Detect centroid distances 2D

detectionCentroidDistances(true)
//Add everything back, and make sure the hierarchy is resolved!
addObjects(tumorAnnos)
addObjects(tumorCells)
resolveHierarchy()

The code works most of the time. Probably 70%? I lack my usual variety of computers to test out whether it is based on my computer - but I do have a project file hosted online I can make available to run the test.

Errors include: Null pointer exception popup in the lower right,

INFO: Starting script at Sat Jun 05 20:54:28 CDT 2021
WARN: Resolving hierarchy that contains 3 annotations and 1236 detections - this may be slow!
ERROR: QuPath exception
WARN: Resolving hierarchy that contains 3 annotations and 1236 detections - this may be slow!
INFO: Script run time: 0.25 seconds
The log file is not hugely informative on that one.

Alternatively, I sometimes see a TMA core error.

ERROR: QuPath exception: Cannot invoke "qupath.lib.objects.PathObject.isTMACore()" because "child" is null
    at qupath.lib.gui.panes.PathObjectHierarchyView$PathObjectTreeItem.getChildren(PathObjectHierarchyView.java:516)
    at qupath.lib.gui.panes.PathObjectHierarchyView$PathObjectTreeItem.isLeaf(PathObjectHierarchyView.java:544)
    at javafx.controls/javafx.scene.control.skin.TreeCellSkin.updateDisclosureNode(Unknown Source)
    at javafx.controls/javafx.scene.control.skin.TreeCellSkin.updateChildren(Unknown Source)
    at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.lambda$new$5(Unknown Source)
    at javafx.controls/com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler.lambda$new$1(Unknown Source)
    at javafx.base/javafx.beans.value.WeakChangeListener.changed(Unknown Source)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(Unknown Source)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown Source)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(Unknown Source)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown Source)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.set(Unknown Source)
    at javafx.graphics/javafx.css.StyleableObjectProperty.set(Unknown Source)
    at javafx.base/javafx.beans.property.ObjectProperty.setValue(Unknown Source)
    at javafx.controls/javafx.scene.control.Labeled.setGraphic(Unknown Source)
    at qupath.lib.gui.panes.PathObjectHierarchyView$PathObjectCell.updateItem(PathObjectHierarchyView.java:423)
    at qupath.lib.gui.panes.PathObjectHierarchyView$PathObjectCell.updateItem(PathObjectHierarchyView.java:413)
    at javafx.controls/javafx.scene.control.TreeCell.updateItem(Unknown Source)
    at javafx.controls/javafx.scene.control.TreeCell.lambda$new$3(Unknown Source)
    at javafx.base/javafx.beans.WeakInvalidationListener.invalidated(Unknown Source)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(Unknown Source)
    at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(Unknown Source)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(Unknown Source)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(Unknown Source)
    at javafx.base/javafx.beans.property.ObjectPropertyBase.set(Unknown Source)
    at javafx.controls/javafx.scene.control.TreeView.setRoot(Unknown Source)
    at qupath.lib.gui.panes.PathObjectHierarchyView.hierarchyChanged(PathObjectHierarchyView.java:563)
    at qupath.lib.gui.panes.PathObjectHierarchyView.lambda$hierarchyChanged$11(PathObjectHierarchyView.java:559)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(Unknown Source)
    at java.base/java.security.AccessController.doPrivileged(Unknown Source)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(Unknown Source)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)

Still other times I see another error:

ERROR: QuPath exception
    at java.base/java.util.LinkedHashMap$LinkedHashIterator.nextNode(Unknown Source)
    at java.base/java.util.LinkedHashMap$LinkedKeyIterator.next(Unknown Source)
    at qupath.lib.objects.PathObject.nDescendants(PathObject.java:475)
    at qupath.lib.objects.PathObjectTools.countDescendants(PathObjectTools.java:200)
    at qupath.lib.objects.PathObject.objectCountPostfix(PathObject.java:190)
    at qupath.lib.objects.PathObject.toString(PathObject.java:224)
    at qupath.lib.gui.panes.PathObjectListCell.updateItem(PathObjectListCell.java:66)
    at qupath.lib.gui.panes.PathObjectListCell.updateItem(PathObjectListCell.java:36)
    at javafx.controls/javafx.scene.control.ListCell.updateItem(Unknown Source)
    at javafx.controls/javafx.scene.control.ListCell.lambda$new$2(Unknown Source)
    at javafx.base/javafx.collections.WeakListChangeListener.onChanged(Unknown Source)
    at javafx.base/com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(Unknown Source)
    at javafx.base/com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(Unknown Source)
    at javafx.base/javafx.collections.ObservableListBase.fireChange(Unknown Source)
    at javafx.base/javafx.collections.ListChangeBuilder.commit(Unknown Source)
    at javafx.base/javafx.collections.ListChangeBuilder.endChange(Unknown Source)
    at javafx.base/javafx.collections.ObservableListBase.endChange(Unknown Source)
    at javafx.base/javafx.collections.ModifiableObservableListBase.setAll(Unknown Source)
    at qupath.lib.gui.panes.AnnotationPane.hierarchyChanged(AnnotationPane.java:382)
    at qupath.lib.gui.panes.AnnotationPane.lambda$hierarchyChanged$7(AnnotationPane.java:352)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(Unknown Source)
    at java.base/java.security.AccessController.doPrivileged(Unknown Source)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(Unknown Source)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)

I am at a loss. The script never seems to actual fail, despite the errors. No objects end up missing, the measurements are created in the correct cells.

Sometimes it will run 7 times in a row. Sometimes it will fail 3 times in a row with the same error.
The region itself is small and shouldn’t cause any problems for my computer.
image
Just the upper left corner of the LuCa FoV image.

I have tried adding fireHierarchyUpdates() everywhere I can think or, and tried Thread.sleep(1000) along with setting the number of CPU cores to 1, as well, with no change to the behavior.

As recommended on the forum, guiscript=true is a viable workaround for the issue.

@petebankhead petebankhead self-assigned this Jun 7, 2021
petebankhead added a commit to petebankhead/qupath that referenced this issue Jun 18, 2021
Attempt to resolve qupath#744

The underlying problem is that concurrent modification exceptions occurred whenever nDescendants() was called by the UI thread while another thread was adding/removing objects.

Adding *more* synchronization to try to overcome this resulted in deadlocks.

This commit tries to resolve the issue in two ways:
- Making the childList a synchronized collection (actually a Set)
- Reducing synchronization on the PathObject itself, and synchonizing more sparingly on the childList

This is hopefully sufficient to avoid simply counting objects in one thread interfering with adding/removing objects in another. Since most adding/removing access is via a PathObjectHierarchy, counting is (I think...) the main concurrency risk, and the resulting code should be more robust.

Along the way, the childList was also given a better default capacity.
@petebankhead petebankhead added this to the v0.3.0 milestone Jun 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants