diff options
author | Joerg Kubitz | 2021-10-03 06:20:20 +0000 |
---|---|---|
committer | Jörg Kubitz | 2021-11-01 14:01:55 +0000 |
commit | dd760156c66832fac12586d4c7a38c80c1657afc (patch) | |
tree | 8ce24a23d8a25013d1d721125cef91a47819ab5a | |
parent | 7cddf9b5fe0cbb3b29588276d552da6a75b4de3b (diff) | |
download | eclipse.platform.ui-dd760156c66832fac12586d4c7a38c80c1657afc.tar.gz eclipse.platform.ui-dd760156c66832fac12586d4c7a38c80c1657afc.tar.xz eclipse.platform.ui-dd760156c66832fac12586d4c7a38c80c1657afc.zip |
Bug 576740 - Add a LRU resource cache for imagesI20211101-1800
Can be disable with system property:
"org.eclipse.jface.resource.cacheSize=0"
Default cacheSize=300.
Removes all hotspots from org.eclipse.jface.resource.ResourceManager
Does not only affect ResourceManager but allows also repeated calls of
setImage() to check for same Image instance and avoid that NO-OP.
Especially from loading images for toolbar:
The problem was that the former reference counting did destroy the
images just before reloading them again.
Caches all Images loaded from filesystem/url during startup of
org.eclipse.sdk.ide. Only the light weight RGBColorDescriptor is
destroyed during startup of org.eclipse.sdk.ide.
Change-Id: I5bfd38dce66ebcc062e140098316fda826e7bf5e
Signed-off-by: Joerg Kubitz <jkubitz-eclipse@gmx.de>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.ui/+/186086
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Sebastian Zarnekow <sebastian.zarnekow@gmail.com>
10 files changed, 552 insertions, 5 deletions
diff --git a/bundles/org.eclipse.jface/build.properties b/bundles/org.eclipse.jface/build.properties index 9b175c1f340..dfed40ab86d 100644 --- a/bundles/org.eclipse.jface/build.properties +++ b/bundles/org.eclipse.jface/build.properties @@ -19,3 +19,4 @@ bin.includes = plugin.properties,\ .options src.includes = about.html source.. = src/ +additional.bundles = org.eclipse.pde.api.tools.annotations diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeferredImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeferredImageDescriptor.java index c1ff14c11a4..a94b003c039 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeferredImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeferredImageDescriptor.java @@ -57,6 +57,7 @@ final class DeferredImageDescriptor extends ImageDescriptor { * @param supplier the supplier of the URL */ DeferredImageDescriptor(boolean useMissingImage, Supplier<URL> supplier) { + super(true); this.supplier = Objects.requireNonNull(supplier); this.useMissingImage = useMissingImage; } @@ -78,4 +79,5 @@ final class DeferredImageDescriptor extends ImageDescriptor { } return ImageDescriptor.createFromURL(url).createImage(returnMissingImageOnError, device); } + } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DerivedImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DerivedImageDescriptor.java index 998843c425e..59c1f907125 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DerivedImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DerivedImageDescriptor.java @@ -40,6 +40,7 @@ final class DerivedImageDescriptor extends ImageDescriptor { * @see SWT#IMAGE_GRAY */ public DerivedImageDescriptor(ImageDescriptor original, int swtFlags) { + super(true); this.original = original; flags = swtFlags; } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeviceResourceDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeviceResourceDescriptor.java index ff09b416c76..3d0bfb0b843 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeviceResourceDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/DeviceResourceDescriptor.java @@ -30,6 +30,47 @@ import org.eclipse.swt.graphics.Device; * @since 3.1 */ public abstract class DeviceResourceDescriptor { + private final boolean shouldBeCached; + + /** + * default constructor with shouldBeCached=false + */ + public DeviceResourceDescriptor() { + this(false); + } + + /** + * @param shouldBeCached Indicates if the resource instance described by the + * descriptor should to be kept by {@link ResourceManager} + * even if all references to the resource are lost (due to + * {@link ResourceManager#destroy(DeviceResourceDescriptor)}).<br> + * Should return true for resources that are costly to + * create (for example by involving I/O Operation). Has + * only an effect if caching is enabled (see + * org.eclipse.jface.resource.JFaceResources#cacheSize). + * Caching relies on {@link #equals(Object)} and + * {@link #hashCode()}. For equal + * DeviceResourceDescriptors the same Resource instance is + * returned by the {@link ResourceManager} instance return + * by + * {@link org.eclipse.jface.resource.JFaceResources#getResources(org.eclipse.swt.widgets.Display)} + * as long as the cache is big enough to cache all + * resources.<br> + * Instances which equal (in terms of + * {@link #equals(Object)}) must have the same + * shouldBeCached mode. + * @see LazyResourceManager + * @since 3.24 + */ + protected DeviceResourceDescriptor(boolean shouldBeCached) { + this.shouldBeCached = shouldBeCached; + + } + + final boolean shouldBeCached() { + return shouldBeCached; + } + /** * Creates the resource described by this descriptor * diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java index 01ec171d0ad..a5251f4828f 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/FileImageDescriptor.java @@ -81,6 +81,7 @@ class FileImageDescriptor extends ImageDescriptor { * the name of the file */ FileImageDescriptor(Class<?> clazz, String filename) { + super(true); this.location = clazz; this.name = filename; } diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java index 21e21d4e576..11caab71401 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/ImageDescriptor.java @@ -73,6 +73,9 @@ public abstract class ImageDescriptor extends DeviceResourceDescriptor { // do nothing } + ImageDescriptor(boolean shouldBeCached) { + super(shouldBeCached); + } /** * Creates and returns a new image descriptor from a file. * diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/JFaceResources.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/JFaceResources.java index 773e5a553ca..d087a4dacec 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/JFaceResources.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/JFaceResources.java @@ -66,7 +66,7 @@ public class JFaceResources { * Map of Display onto DeviceResourceManager. Holds all the resources for * the associated display. */ - private static final Map<Display,DeviceResourceManager> registries = new HashMap<>(); + private static final Map<Display, ResourceManager> registries = new HashMap<>(); /** * The symbolic font name for the banner font (value @@ -196,19 +196,29 @@ public class JFaceResources { } /** + * 300 is big enough to cache common images of eclipse IDE, and small enough to + * not blow OS. + */ + private static final int cacheSize = Integer.getInteger("org.eclipse.jface.resource.cacheSize", 300).intValue(); //$NON-NLS-1$ + + /** * Returns the global resource manager for the given display * * @since 3.1 * - * @param toQuery - * display to query + * @param toQuery display to query * @return the global resource manager for the given display */ public static ResourceManager getResources(final Display toQuery) { ResourceManager reg = registries.get(toQuery); if (reg == null) { - final DeviceResourceManager mgr = new DeviceResourceManager(toQuery); + final ResourceManager mgr; + if (cacheSize == 0) { + mgr = new DeviceResourceManager(toQuery); + } else { + mgr = new LazyResourceManager(cacheSize, new DeviceResourceManager(toQuery)); + } reg = mgr; registries.put(toQuery, mgr); toQuery.disposeExec(() -> { diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/LazyResourceManager.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/LazyResourceManager.java new file mode 100644 index 00000000000..939da7fa00c --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/LazyResourceManager.java @@ -0,0 +1,124 @@ +package org.eclipse.jface.resource; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.pde.api.tools.annotations.NoReference; +import org.eclipse.swt.graphics.Device; +import org.eclipse.swt.graphics.Image; + +/** + * A LRU based ResourceManager Wrapper. Not to be used by clients. + */ +@NoReference +public class LazyResourceManager extends ResourceManager { + /** + * This LRU Map only holds the DeviceResourceDescriptors which are not + * referenced otherwise anymore. The Resources itself are only cached by the + * parent ResourceManager. + */ + private static class LruMap extends LinkedHashMap<DeviceResourceDescriptor, ResourceManager> { + private static final long serialVersionUID = 1L; + int cacheSize; + + LruMap(int cacheSize) { + super(cacheSize, 0.75f, true); // last access-order + this.cacheSize = cacheSize; + } + + @Override + protected boolean removeEldestEntry(java.util.Map.Entry<DeviceResourceDescriptor, ResourceManager> eldest) { + boolean remove = size() > cacheSize; + if (remove) { + // destroy resource which was not used recently: + eldest.getValue().destroy(eldest.getKey()); + } + return remove; + } + } + + private final ResourceManager parent; + private final LruMap unreferenced; + private final Map<DeviceResourceDescriptor, Integer> refCount; + + /** + * @param cacheSize the lru cache size + * @param parent ResourceManager + */ + public LazyResourceManager(int cacheSize, ResourceManager parent) { + this.parent = parent; + this.unreferenced = new LruMap(cacheSize); + this.refCount = new HashMap<>(); + } + + @Override + public Device getDevice() { + return parent.getDevice(); + } + + /** + * @param descriptor + * @return if a resource based on this descriptor should be cached. + */ + private boolean shouldBeCached(DeviceResourceDescriptor descriptor) { + return descriptor.shouldBeCached(); + } + + @Override + protected Image getDefaultImage() { + return parent.getDefaultImage(); + } + + private static Integer createOrIncrease(@SuppressWarnings("unused") DeviceResourceDescriptor key, Integer refs) { + return Integer.valueOf(refs == null ? 1 : refs.intValue() + 1); + } + + private static Integer decreaseOrRemove(@SuppressWarnings("unused") DeviceResourceDescriptor key, Integer refs) { + return refs.intValue() == 1 ? null : Integer.valueOf(refs.intValue() - 1); + + } + @Override + public Object create(DeviceResourceDescriptor descriptor) { + if (!shouldBeCached(descriptor)) { + return parent.create(descriptor); + } + int updatedRefs = refCount.compute(descriptor, LazyResourceManager::createOrIncrease).intValue(); + if (updatedRefs == 1) { + ResourceManager cached = unreferenced.remove(descriptor); + if (cached == null) { + return parent.create(descriptor); + } + // referenced again + } else { + assert !unreferenced.containsKey(descriptor); + } + return parent.find(descriptor); + } + + @Override + public void destroy(DeviceResourceDescriptor descriptor) { + if (!shouldBeCached(descriptor)) { + parent.destroy(descriptor); + return; + } + Integer refsLeft = refCount.computeIfPresent(descriptor, LazyResourceManager::decreaseOrRemove); + if (refsLeft == null) { + // defer destroy: + ResourceManager old = unreferenced.put(descriptor, parent); + assert old == null; + } + } + + @Override + public Object find(DeviceResourceDescriptor descriptor) { + if (!shouldBeCached(descriptor)) { + return parent.find(descriptor); + } + if (refCount.containsKey(descriptor)) { + return parent.find(descriptor); + } + return null; + } + +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java index d5b577bf7e6..c150acd09f6 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java @@ -11,7 +11,7 @@ * Contributors: * IBM Corporation - initial API and implementation * Patrik Suzzi <psuzzi@gmail.com> - Bug 483465 - * Christoph Läubrich - Bug 567898 - [JFace][HiDPI] ImageDescriptor support alternative naming scheme for high dpi + * Christoph Läubrich - Bug 567898 - [JFace][HiDPI] ImageDescriptor support alternative naming scheme for high dpi *******************************************************************************/ package org.eclipse.jface.resource; @@ -107,6 +107,7 @@ class URLImageDescriptor extends ImageDescriptor { * The URL to load the image from. Must be non-null. */ URLImageDescriptor(URL url) { + super(true); this.url = url.toExternalForm(); } @@ -333,4 +334,5 @@ class URLImageDescriptor extends ImageDescriptor { } return result; } + } diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/LazyResourceManagerTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/LazyResourceManagerTest.java new file mode 100644 index 00000000000..980ba82cbab --- /dev/null +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/LazyResourceManagerTest.java @@ -0,0 +1,362 @@ +/******************************************************************************* + * Copyright (c) 2004, 2016 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Patrik Suzzi <psuzzi@gmail.com> - Bug 490700 + *******************************************************************************/ +package org.eclipse.jface.tests.images; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.resource.DeviceResourceDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LazyResourceManager; +import org.eclipse.jface.resource.ResourceManager; +import org.eclipse.swt.graphics.Device; +import org.eclipse.swt.graphics.Image; + +import junit.framework.TestCase; + +public class LazyResourceManagerTest extends TestCase { + private static class CachableTestDescriptor extends DeviceResourceDescriptor { + CachableTestDescriptor() { + super(true); + } + + @Override + public Object createResource(Device device) { + return null; + } + + @Override + public void destroyResource(Object previouslyCreatedObject) { + } + } + + private static class UncachableTestDescriptor extends DeviceResourceDescriptor { + UncachableTestDescriptor() { + super(false); + } + + @Override + public Object createResource(Device device) { + return null; + } + + @Override + public void destroyResource(Object previouslyCreatedObject) { + } + } + + private static class TestResourceManager extends ResourceManager { + private Device device; + private Map<DeviceResourceDescriptor, AtomicReference<DeviceResourceDescriptor>> objects = new HashMap<>(); + private Map<DeviceResourceDescriptor, Integer> refCount = new HashMap<>(); + private Image defaultImage; + + @Override + public Device getDevice() { + return device; + } + + @Override + public AtomicReference<DeviceResourceDescriptor> create(DeviceResourceDescriptor descriptor) { + AtomicReference<DeviceResourceDescriptor> newInstance = new AtomicReference<>(descriptor); + AtomicReference<DeviceResourceDescriptor> previous = objects.putIfAbsent(descriptor, newInstance); + refCount.compute(descriptor, (k, refs) -> Integer.valueOf((refs == null ? 0 : refs.intValue()) + 1)); + return previous == null ? newInstance : previous; + } + + @Override + public void destroy(DeviceResourceDescriptor descriptor) { + if (!refCount.containsKey(descriptor)) { + throw new RuntimeException("not created"); + } + int refs = refCount.get(descriptor).intValue(); + refs--; + refCount.put(descriptor, Integer.valueOf(refs)); + if (refs == 0) { + objects.remove(descriptor); + } + + } + + @Override + protected Image getDefaultImage() { + return defaultImage; + } + + @Override + public AtomicReference<DeviceResourceDescriptor> find(DeviceResourceDescriptor descriptor) { + return objects.get(descriptor); + } + + public void setDevice(Device device) { + this.device = device; + } + + public void setDefaultImage(Image defaultImage) { + this.defaultImage = defaultImage; + } + + } + + public LazyResourceManagerTest(String name) { + super(name); + } + + public void testDefaultImage() { + // note, we must touch the class to ensure the static initialer runs + // so the image registry is up to date + Dialog.getBlockedHandler(); + + @SuppressWarnings("deprecation") + String[] imageNames = new String[] { Dialog.DLG_IMG_ERROR, Dialog.DLG_IMG_INFO, Dialog.DLG_IMG_QUESTION, + Dialog.DLG_IMG_WARNING, Dialog.DLG_IMG_MESSAGE_ERROR, Dialog.DLG_IMG_MESSAGE_INFO, + Dialog.DLG_IMG_MESSAGE_WARNING }; + + ImageRegistry reg = JFaceResources.getImageRegistry(); + + TestResourceManager tst = new TestResourceManager(); + LazyResourceManager mgr = new LazyResourceManager(2, tst) { + { + // getDefaultImage() is not not public + assertSame(tst.getDefaultImage(), getDefaultImage()); + } + }; + for (String imageName : imageNames) { + Image image = reg.get(imageName); + tst.setDefaultImage(image); + tst.setDevice(image.getDevice()); + } + assertNotNull(tst.getDevice()); + assertSame(tst.getDevice(), mgr.getDevice()); + + } + + public void testUncachable() { + TestResourceManager tst = new TestResourceManager(); + LazyResourceManager mgr = new LazyResourceManager(2, tst); + + assertSame(tst.getDevice(), mgr.getDevice()); + DeviceResourceDescriptor descriptor1 = new UncachableTestDescriptor(); + { + Object created1 = mgr.create(descriptor1); + AtomicReference<DeviceResourceDescriptor> expected1 = tst.find(descriptor1); + assertSame(expected1, created1); + assertSame(expected1, mgr.find(descriptor1)); + mgr.destroy(descriptor1); + assertDestroyed(expected1, mgr, tst, descriptor1); + } + } + + void assertCached(AtomicReference<DeviceResourceDescriptor> previousInstance, LazyResourceManager mgr, + TestResourceManager tst, DeviceResourceDescriptor rd) { + assertNotNull(previousInstance); + assertNull(mgr.find(rd)); // destroyed but + assertNotNull(tst.find(rd)); // cached +// would be best to ask mgr to creat a new resource, but that would influence the LRU order: +// Object created = mgr.create(rd); +// mgr.destroy(rd); + // assume mgr would create the instance given by parent tst: + AtomicReference<DeviceResourceDescriptor> created = tst.find(rd); + assertSame(previousInstance, created); + } + + void assertAlife(AtomicReference<DeviceResourceDescriptor> previousInstance, LazyResourceManager mgr, + TestResourceManager tst, DeviceResourceDescriptor rd) { + assertNotNull(previousInstance); + assertSame(previousInstance, mgr.find(rd)); + assertSame(previousInstance, tst.find(rd)); + } + + void assertDestroyed(AtomicReference<DeviceResourceDescriptor> previousInstance, LazyResourceManager mgr, + TestResourceManager tst, DeviceResourceDescriptor rd) { + assertNotNull(previousInstance); + assertNull(mgr.find(rd)); // destroyed and + assertNull(tst.find(rd)); // cached +// would be best to ask mgr to creat a new resource, but that would influence the LRU order: +// Object created = mgr.create(rd); +// mgr.destroy(rd); +// assertNotSame(previousInstance, created); + // assume mgr would create the instance given by parent tst: + AtomicReference<DeviceResourceDescriptor> created = tst.find(rd); + assertNotSame(previousInstance, created); + } + + /** + * Creates multiple resources for 2 Descriptors. Only 1 of them can be cached + **/ + @SuppressWarnings("unchecked") + public void testLazyResourceManagerRefCounting() { + TestResourceManager tst = new TestResourceManager(); + LazyResourceManager mgr = new LazyResourceManager(1, tst); + + assertSame(tst.getDevice(), mgr.getDevice()); + DeviceResourceDescriptor descriptor1 = new CachableTestDescriptor(); + AtomicReference<DeviceResourceDescriptor> expected1; + { + expected1 = (AtomicReference<DeviceResourceDescriptor>) mgr.create(descriptor1); // create first ref + assertAlife(expected1, mgr, tst, descriptor1); + mgr.create(descriptor1); // create second ref + assertAlife(expected1, mgr, tst, descriptor1); + mgr.destroy(descriptor1); // destroy second + assertAlife(expected1, mgr, tst, descriptor1); + mgr.destroy(descriptor1); // destroy first + } + assertCached(expected1, mgr, tst, descriptor1); + DeviceResourceDescriptor descriptor2 = new CachableTestDescriptor(); + { + // exceeded cache capacity: + mgr.create(descriptor2); + mgr.destroy(descriptor2); + } + assertDestroyed(expected1, mgr, tst, descriptor1); + { + Object created1_ = mgr.create(descriptor1); + AtomicReference<DeviceResourceDescriptor> expected1_ = tst.find(descriptor1); // == descriptor1 + assertNotSame(expected1, expected1_); + assertSame((expected1).get(), (expected1_).get()); // same descriptor + assertSame(expected1_, created1_); + assertAlife(expected1_, mgr, tst, descriptor1); + mgr.destroy(descriptor1); + assertCached(expected1_, mgr, tst, descriptor1); + } + } + + /** Creates resources for 3 Descriptors. Only 2 of them can be cached **/ + public void testLazyResourceManager() { + TestResourceManager tst = new TestResourceManager(); + LazyResourceManager mgr = new LazyResourceManager(2, tst); + + assertSame(tst.getDevice(), mgr.getDevice()); + DeviceResourceDescriptor descriptor1 = new CachableTestDescriptor(); + AtomicReference<DeviceResourceDescriptor> expected1; + { + Object created1 = mgr.create(descriptor1); + expected1 = tst.find(descriptor1); + assertSame(expected1, created1); + assertSame(expected1, mgr.find(descriptor1)); + mgr.destroy(descriptor1); + } + assertCached(expected1, mgr, tst, descriptor1); + DeviceResourceDescriptor descriptor2 = new CachableTestDescriptor(); + AtomicReference<DeviceResourceDescriptor> expected2; + { + Object created = mgr.create(descriptor2); + expected2 = tst.find(descriptor2); + assertSame(expected2, created); + assertSame(expected2, mgr.find(descriptor2)); + mgr.destroy(descriptor2); + assertCached(expected2, mgr, tst, descriptor2); + mgr.create(descriptor2); + mgr.destroy(descriptor2); + assertCached(expected2, mgr, tst, descriptor2); + mgr.create(descriptor2); + mgr.destroy(descriptor2); + assertCached(expected2, mgr, tst, descriptor2); + } + assertCached(expected1, mgr, tst, descriptor1); + DeviceResourceDescriptor descriptor3 = new CachableTestDescriptor(); + AtomicReference<DeviceResourceDescriptor> expected3; + { + Object created = mgr.create(descriptor3); + expected3 = tst.find(descriptor3); + assertSame(expected3, created); + assertSame(expected3, mgr.find(descriptor3)); + mgr.destroy(descriptor3); + assertCached(expected3, mgr, tst, descriptor3); + } + assertDestroyed(expected1, mgr, tst, descriptor1); // lru size exceeded: not cached anymore + assertCached(expected2, mgr, tst, descriptor2); + assertCached(expected3, mgr, tst, descriptor3); + { + Object created1_ = mgr.create(descriptor1); + AtomicReference<DeviceResourceDescriptor> expected1_ = tst.find(descriptor1); // == descriptor1 + assertNotSame(expected1, expected1_); // different resources + assertSame(((AtomicReference<?>) expected1).get(), ((AtomicReference<?>) expected1_).get()); // same + // descriptor + assertSame(expected1_, created1_); + assertSame(expected1_, mgr.find(descriptor1)); + mgr.destroy(descriptor1); + assertCached(expected1_, mgr, tst, descriptor1);// cached again + } + assertCached(expected3, mgr, tst, descriptor3); + assertDestroyed(expected2, mgr, tst, descriptor2); // lru size exceeded: not cached anymore + } + + /** + * Creates resources for 3 Descriptors. Only the 2 last recently used should be + * cached + **/ + public void testLazyResourceManagerLRU() { + TestResourceManager tst = new TestResourceManager(); + LazyResourceManager mgr = new LazyResourceManager(2, tst); + + assertSame(tst.getDevice(), mgr.getDevice()); + DeviceResourceDescriptor descriptor1 = new CachableTestDescriptor(); + AtomicReference<DeviceResourceDescriptor> expected1; + { + Object created1 = mgr.create(descriptor1); + expected1 = tst.find(descriptor1); + assertSame(expected1, created1); + assertSame(expected1, mgr.find(descriptor1)); + mgr.destroy(descriptor1); + } + assertCached(expected1, mgr, tst, descriptor1); + DeviceResourceDescriptor descriptor2 = new CachableTestDescriptor(); + AtomicReference<DeviceResourceDescriptor> expected2; + { + Object created = mgr.create(descriptor2); + expected2 = tst.find(descriptor2); + assertSame(expected2, created); + assertSame(expected2, mgr.find(descriptor2)); + mgr.destroy(descriptor2); + assertCached(expected2, mgr, tst, descriptor2); + } + assertCached(expected1, mgr, tst, descriptor1); + DeviceResourceDescriptor descriptor3 = new CachableTestDescriptor(); + AtomicReference<DeviceResourceDescriptor> expected3; + { + Object created = mgr.create(descriptor3); + expected3 = tst.find(descriptor3); + assertSame(expected3, created); + assertSame(expected3, mgr.find(descriptor3)); + mgr.destroy(descriptor3); + assertCached(expected3, mgr, tst, descriptor3); + } + { // *use* 2 again + Object created = mgr.create(descriptor2); + AtomicReference<DeviceResourceDescriptor> expected = tst.find(descriptor2); + assertSame(expected, created); + assertSame(expected, mgr.find(descriptor2)); + mgr.destroy(descriptor2); + assertCached(expected, mgr, tst, descriptor2); + } + assertDestroyed(expected1, mgr, tst, descriptor1); // lru size exceeded: not cached anymore + assertCached(expected2, mgr, tst, descriptor2); + assertCached(expected3, mgr, tst, descriptor3); + { + Object created1_ = mgr.create(descriptor1); + AtomicReference<DeviceResourceDescriptor> expected1_ = tst.find(descriptor1); // == descriptor1 + assertSame(expected1_, created1_); + assertSame(expected1_, mgr.find(descriptor1)); + mgr.destroy(descriptor1); + assertCached(expected1_, mgr, tst, descriptor1); + } + assertDestroyed(expected3, mgr, tst, descriptor3); // lru size exceeded: not cached anymore + assertCached(expected2, mgr, tst, descriptor2); // 2 still cached, because recently used + } +} |