-
Notifications
You must be signed in to change notification settings - Fork 190
Using ImageHandleManager for managing multiple Image handles in Image class #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?
Using ImageHandleManager for managing multiple Image handles in Image class #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.
| return function.apply(temporaryHandle); | ||
| } finally { | ||
| temporaryHandle.destroy(); | ||
| imageHandleManager.destroy(temporaryHandle); |
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 do you need to destroy the handle via the handle manager even though it was not created via the manager (so it will not be present in its handle map anyway)?
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.
It will. All the ImageHandles are registered in the manager. It is in the constructor of ImageHandle. Maybe we should only allow creation of ImageHandle using the manager then. This will simplify it. We want to register all the ImageHandles as we actively perform cleanup of dangling ImageHandles.
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.
The dangling ImageHandles can be those which were created temporarily for obtaining the ImageData or for some other purpose. We want to track all the ImageHandles in the Manager.
| ImageHandle handle = initializeHandleFromSource(new ZoomContext(zoomToRetrieve)); | ||
| ImageData data = handle.getImageData(); | ||
| handle.destroy(); | ||
| imageHandleManager.destroy(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.
Same as above: why do you need to destroy the handle via the handle manager even though it was not created via the manager (so it will not be present in its handle map anyway)?
This applies to all newImageData methods.
| return imageHandle; | ||
| } | ||
| return zoomLevelToImageHandle.get(targetZoom); | ||
| return imageHandleManager.getImageHandle(targetZoom); |
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.
This code looks a bit weird (but also did before) because we call a _new_ImageHandle method and then return an existing one. From my understanding, this line of code should never be reached anyway. In case there is a memGC, there should also be an image handle for it, so that newImageHandle will never be called unless the zoom of that handle is not fitting (and then the if block above is executed).
| ImageHandle imageHandle = imageHandleManager.getImageHandle(zoom); | ||
| if (imageHandle != null) { | ||
| return imageHandle.getImageData(); |
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.
This code is still prone to race conditions: there might not be a handle yet, then another thread comes and creates that handle, but since we have not seen that handle here, we call imageProvider.newImage, which will expensively create another handle, extract image data out of it and then cache that image data (although the cached image data will never be used as there is a handle already, so that part of memory is effectively dead). Is that intended?
| long newHandle = drawable.internal_new_GC(newData); | ||
| if (newHandle == 0) SWT.error(SWT.ERROR_NO_HANDLES); | ||
| init(drawable, newData, newHandle); | ||
| initWithImageHandle(drawable, newData, newHandle, 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.
Why is this change to GC necessary? Is it a bug in the existing implementation?
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.
Yes this method is created from the earlier implementation of the PlainImageDataProvider as in case it uses GC to scale, it uses this specific method. In the init method of GC, it uses the Image.win32_getHandle, which would want to create a handle and since we already are in the middle of creation of the handle it will fail. Since we also had a specific method in the past for this case, the idea is to use the ImageHandle we just obtained and pass that directly instead of calling win32_getHandle.
| final ImageHandle imageHandleFuture = zoomLevelToImageHandle.get(zoom); | ||
| if (imageHandleFuture == null) { | ||
| return null; | ||
| } | ||
| return imageHandleFuture; |
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.
There is no future involved here and the value of zoomLevelToHandle.get(zoom) could directly be returned, couldn't it?
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.
My bad, forgot to rename it. Went with approach of Futures earlier.
| private Map<Integer, ImageHandle> zoomLevelToImageHandle = new ConcurrentHashMap<>(); | ||
| private Set<ImageHandle> imageHandles = new HashSet<>(); |
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 do we need two separate data structure managing image handles? They are managed in different ways and will mostly (but not competely) contains the same handles.
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.
The Set contains all the distinct imageHandles created for the image temporary and permanent ones. Any dangling imageHandle can be tracked here and disposed upon cleanups.
| return zoomLevelToImageHandle.containsKey(zoom); | ||
| } | ||
|
|
||
| boolean hasNoBaseHandle() { |
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.
Should this be hasNoHandle()? It's unclear what a "base" handle is supposed to be.
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.
Fair point.
| void destroy(ImageHandle imageHandle) { | ||
| zoomLevelToImageHandle.remove(imageHandle.zoom, imageHandle); | ||
| imageHandle.destroy(); | ||
| imageHandles.remove(imageHandle); | ||
| cleanupDanglingImageHandles(); | ||
| } |
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.
This method feels wrong to me: either the ImageHandleManager manages the handles and does not auto destroy (with the other method accepting a Predicate) or a handle is managed separately, but then it should not be artificially be passed over this manager.
If I am not mistaken, at least the imageHandles set, the register method and the destroy(ImageHandle) should be removed.
93108dc to
e9cb651
Compare
This commit adds a class ImageHandleManager in Image to manage multiple ImageHandle for different zoom hence encapsulating the usages and operations based on zoomLevelToImageHandle.
This commit makes the ImageHandle creation and ImageData caching process synchronous to avoid any racing condition and inefficient memory usages.
e9cb651 to
0e6ad0d
Compare
Test Results (win32) 27 files - 7 27 suites - 7 4m 12s ⏱️ -5s For more details on these failures, see this check. Results for commit 0e6ad0d. ± Comparison against base commit 383ed2a. This pull request removes 57 tests. |
This PR introduces ImageHandleManager, an inner class to Image responsible for managing the storage, creation and manipulation of ImageHandles. This class provides methods which can be used to dead with ImageHandle instances inside an image for managing it with convenience while hiding the complexity of how the ImageHandle lifecycle is managed inside itself.
All the places where zoomToImageHandle map is accessed for any purposes is replaced by the wrapper methods provided by ImageHandleManager. Each image initializes a final field imageHandleManager with the instance of ImageHandleManager. Every instance of ImageHandle created within the Image object is registered in the manager itself and the manager is responsible for cleaning the dangling resources when are not supposed to be used anymore or when a cleanup is needed. Also the manager creates handles in a thread-safe way, making sure no 2 consumers create the same handle at the same time.
This fixes vi-eclipse/Eclipse-Platform#562