-
Notifications
You must be signed in to change notification settings - Fork 190
Implemented Double-Checked Locking in ImagehandleManager #2905
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
base: master
Are you sure you want to change the base?
Implemented Double-Checked Locking in ImagehandleManager #2905
Conversation
HeikoKlare
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the idea to wrap the handle-for-zoom mapping into a separate class. That clearly assigns all related management functionality to that class. I would be in favor of extracting the pure refactoring of the management functionality in a dedicated class into a separate PR. That PR will just improve code quality and comprehensibility and not improve thread safety.
You find several questions and proposals in the detailed comments. The probably most essential point is that to my understanding the implementation is not fully thread safe.
In addition, I am not sure if the concept of using a synchronized data structure is fully sufficient for all cases here (e.g., if we also include the rest of the API such as getBounds() and getImageData()). At least the complexity could increase such that ensuring correctness and maintainability becomes complex. Maybe we better see that when we adapt those methods as well, but I could imagine that simply synchronizing write access might be sufficient (as we don't expect multiple consumers to write for different zooms) and provides reduced compexity and error proneness.
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/GC.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/graphics/Image.java
Outdated
Show resolved
Hide resolved
e9cb651 to
0e6ad0d
Compare
1494ed0 to
22f6ab7
Compare
This commit makes the ImageHandle creation and ImageData caching process synchronous to avoid any racing condition and inefficient memory usages.
22f6ab7 to
4da78e2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As written here #2905 (review), getBounds() is also affected by potential race conditions.
I went through all call sequences of methods in ImageHandleManager and only found getBounds() with a potential remaining race condition. Still it would be good to have someone confirm this by checking the sequences.
In addition, I wonder why/if we need this runSynchronized method to synchronize on the map of ImageHandleManager or whether we cannot simply synchronize on ImageHandleManager itself? Then we would not need that method.
Edit: Maybe it would even be better to keep the synchronization inside ImageHandleManager but not make it explicit via an runSynchronized method but having a specific method for the use case that does the synchronizaiton, i.e., have something like a <R> R executeOnHandle(int zoom, Function<Optional<ImageHandle>,R> execution) method for the scenario(s) using runSynchronized like getting the image data.
|
I have not looked into the full details here but using |
|
The point is that resources used to be accessible from any thread (even though it is not recommended, but it worked and consumers used it) and this behavior broke when introducing the multi-handle support. This change is just restoring that capability. |
Looking at the If we still want to support this I think synchronized primitives are not the right way and instead concurrent data-structures must be used instead and we are prone to deadlocks here. |
| private final ImageHandleManager imageHandleManager = new ImageHandleManager(); | ||
|
|
||
| private class ImageHandleManager { | ||
| private Map<Integer, DestroyableImageHandle> zoomLevelToImageHandle = new HashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| private Map<Integer, DestroyableImageHandle> zoomLevelToImageHandle = new ConurrentHashMap<>(); |
| synchronized (zoomLevelToImageHandle) { | ||
| imageHandle = (DestroyableImageHandle) get(zoom); | ||
| if (imageHandle == null) { | ||
| imageHandle = creator.get(); | ||
| zoomLevelToImageHandle.put(zoom, imageHandle); | ||
| } | ||
| return imageHandle; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use zoomLevelToImageHandle.computeIfAbsent(...) or compute(...) on the concurrent map instead of own synchronization primitives
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
93108dc7d4
That's how I pretty much started off earlier but since we also need to implement this for getImageData which either retrieves it from ImageHandle#getImageData or creates an ImageData and caches it; we needed different mechanism and overall it made sense to use a consistent locking mechanism rather than using 2 different approaches in 2 different places. That's why we decided to go with double-checked locking using synchronized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must confess I don't understand the befit of this approach and why it should not work for getImageData.
Assume Thread 1 is entering here into the map.computeIfAbsent.
Now Thread 2 is entering getImageData and will be blocked on the get call until Thread 1 completes, what has the same effect as Thread 1 entering the synchronized and Thread 2 is hitting the null and then entering the synchronization and needs to wait until Thread 1 finishes.
The main difference here is, that if we have more threads then with your approach ALL threads will be blocked regardless of zoomlevel (both in getImageData and getOrCreate) even if they aim for different zoom levels!
... not talking even about the case that we have a race condition between two threads one that is removing it an one that is getting it (what will make a possibly destroyed image handle visible to the getting thread.
So actually I think one even needs to use Map.compute here and do two checks: If current value is null or current value is disposed we need to create a new handle...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why it should not work for
getImageData.
getImageData() is not doing an atomic call on the ImageHandleManager, which would be necessary to make a synchronization via the data structure. Maybe there is some possibility to refactor the code such that it is wrapped into an atomic data structure access.
ALL threads will be blocked regardless of zoomlevel (both in
getImageDataandgetOrCreate) even if they aim for different zoom levels
That's correct but not a problem at all. This synchronization is only necessary to avoid issues due to the extremely rare case of a race condition, so there is no necessity to ensure that access to the data structure with different zooms concurrently works in an efficient way.
That's true, there is nothing explicitly implemented to ensure thread-safety. However, most of the functionality was effectively thread-safe as after object initialization most of the state was effectively final (in particular the handles themselves), such that without any additional effort all access scenarios that did not modify state worked find in a concurrent access scenario. |
This PR handles the creation of handles / cached ImageData per zoom for Image objects in a thread-safe way by the means of double-checked locking. The implementation extends the ImageHandleManager in the following way:
runSynchronizedfor to run a block of code synchronously. This is utilized while calling getImageData to ensure thread safety.Depends on #2927
This fixes vi-eclipse/Eclipse-Platform#562