Ticket #13120: patch-fix-13120.patch
| File patch-fix-13120.patch, 22.0 KB (added by , 10 years ago) |
|---|
-
src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java index 88b48d0..c2fb5c4 100644
a b import org.openstreetmap.josm.gui.progress.ProgressMonitor; 89 89 import org.openstreetmap.josm.gui.util.GuiHelper; 90 90 import org.openstreetmap.josm.io.WMSLayerImporter; 91 91 import org.openstreetmap.josm.tools.GBC; 92 import org.openstreetmap.josm.tools.MemoryManager; 93 import org.openstreetmap.josm.tools.MemoryManager.MemoryHandle; 94 import org.openstreetmap.josm.tools.MemoryManager.NotEnoughMemoryException; 92 95 93 96 /** 94 97 * Base abstract class that supports displaying images provided by TileSource. It might be TMS source, WMS or WMTS … … implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 693 696 if (tileSource == null) { 694 697 throw new IllegalArgumentException(tr("Failed to create tile source")); 695 698 } 696 checkLayerMemoryDoesNotExceedMaximum();697 699 // check if projection is supported 698 700 projectionChanged(null, Main.getProjection()); 699 701 initTileSource(this.tileSource); … … implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 702 704 703 705 @Override 704 706 protected LayerPainter createMapViewPainter(MapViewEvent event) { 705 return new CompatibilityModeLayerPainter() { 706 @Override 707 public void detachFromMapView(MapViewEvent event) { 708 event.getMapView().removeMouseListener(adapter); 709 MapView.removeZoomChangeListener(AbstractTileSourceLayer.this); 710 super.detachFromMapView(event); 711 } 712 }; 707 return new TileSourcePainter(); 713 708 } 714 709 715 710 /** … … implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 733 728 } 734 729 } 735 730 736 @Override737 protected long estimateMemoryUsage() {738 return 4L * tileSource.getTileSize() * tileSource.getTileSize() * estimateTileCacheSize();739 }740 741 731 protected int estimateTileCacheSize() { 742 732 Dimension screenSize = GuiHelper.getMaximumScreenSize(); 743 733 int height = screenSize.height; … … implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 1939 1929 super.destroy(); 1940 1930 adjustAction.destroy(); 1941 1931 } 1932 1933 private class TileSourcePainter extends CompatibilityModeLayerPainter { 1934 /** 1935 * The memory handle that will hold our tile source. 1936 */ 1937 private MemoryHandle<?> memory; 1938 1939 @Override 1940 public void paint(MapViewGraphics graphics) { 1941 allocateCacheMemory(); 1942 if (memory != null) { 1943 super.paint(graphics); 1944 } 1945 } 1946 1947 private void allocateCacheMemory() { 1948 if (memory == null) { 1949 MemoryManager manager = MemoryManager.getInstance(); 1950 if (manager.isAvailable(getEstimatedCacheSize())) { 1951 try { 1952 memory = manager.allocateMemory("tile source layer", getEstimatedCacheSize(), () -> new Object()); 1953 } catch (NotEnoughMemoryException e) { 1954 Main.warn("Could not allocate tile source memory", e); 1955 } 1956 } 1957 } 1958 } 1959 1960 protected long getEstimatedCacheSize() { 1961 return 4L * tileSource.getTileSize() * tileSource.getTileSize() * estimateTileCacheSize(); 1962 } 1963 1964 @Override 1965 public void detachFromMapView(MapViewEvent event) { 1966 event.getMapView().removeMouseListener(adapter); 1967 MapView.removeZoomChangeListener(AbstractTileSourceLayer.this); 1968 super.detachFromMapView(event); 1969 if (memory != null) { 1970 memory.free(); 1971 } 1972 } 1973 } 1942 1974 } -
src/org/openstreetmap/josm/gui/layer/Layer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/Layer.java b/src/org/openstreetmap/josm/gui/layer/Layer.java index b4f5797..2bab2e4 100644
a b public abstract class Layer extends AbstractMapViewPaintable implements Destroya 158 158 * It is always called in the event dispatching thread. 159 159 * Note that Main.map is null as long as no layer has been added, so do 160 160 * not execute code in the constructor, that assumes Main.map.mapView is 161 * not null. Instead override this method.161 * not null. 162 162 * 163 * This implementation provides check, if JOSM will be able to use Layer. Layers 164 * using a lot of memory, which do know in advance, how much memory they use, should 165 * override {@link #estimateMemoryUsage() estimateMemoryUsage} method and give a hint. 166 * 167 * This allows for preemptive warning message for user, instead of failing later on 168 * 169 * Remember to call {@code super.hookUpMapView()} when overriding this method 163 * If you need to execute code when this layer is added to the map view, use 164 * {@link #attachToMapView(org.openstreetmap.josm.gui.layer.MapViewPaintable.MapViewEvent)} 170 165 */ 171 166 public void hookUpMapView() { 172 checkLayerMemoryDoesNotExceedMaximum();173 }174 175 /**176 * Checks that the memory required for the layers is no greather than the max memory.177 */178 protected static void checkLayerMemoryDoesNotExceedMaximum() {179 // calculate total memory needed for all layers180 long memoryBytesRequired = 50L * 1024L * 1024L; // assumed minimum JOSM memory footprint181 for (Layer layer: Main.getLayerManager().getLayers()) {182 memoryBytesRequired += layer.estimateMemoryUsage();183 }184 if (memoryBytesRequired > Runtime.getRuntime().maxMemory()) {185 throw new IllegalArgumentException(186 tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M "187 + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n"188 + "Currently you have {1,number,#}MB memory allocated for JOSM",189 memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024));190 }191 167 } 192 168 193 169 /** … … public abstract class Layer extends AbstractMapViewPaintable implements Destroya 588 564 589 565 /** 590 566 * @return bytes that the tile will use. Needed for resource management 567 * @deprecated Not used any more. 591 568 */ 569 @Deprecated 592 570 protected long estimateMemoryUsage() { 593 571 return 0; 594 572 } -
new file src/org/openstreetmap/josm/tools/MemoryManager.java
diff --git a/src/org/openstreetmap/josm/tools/MemoryManager.java b/src/org/openstreetmap/josm/tools/MemoryManager.java new file mode 100644 index 0000000..0cd2e5d
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.tools; 3 import static org.openstreetmap.josm.tools.I18n.tr; 4 5 import java.text.MessageFormat; 6 import java.util.ArrayList; 7 import java.util.List; 8 import java.util.function.Supplier; 9 10 import org.openstreetmap.josm.Main; 11 12 /** 13 * This class allows all components of JOSM to register reclaimable amounts to memory. 14 * <p> 15 * It can be used to hold imagery caches or other data that can be reconstructed form disk/web if required. 16 * <p> 17 * Reclaimable storage implementations may be added in the future. 18 * 19 * @author Michael Zangl 20 * @since xxx 21 */ 22 public class MemoryManager { 23 /** 24 * assumed minimum JOSM memory footprint 25 */ 26 private static final long JOSM_CORE_FOOTPRINT = 50L * 1024L * 1024L; 27 28 private static final MemoryManager INSTANCE = new MemoryManager(); 29 30 private ArrayList<MemoryHandle<?>> activeHandles = new ArrayList<>(); 31 32 protected MemoryManager() { 33 } 34 35 /** 36 * Allocates a basic, fixed memory size. 37 * <p> 38 * If there is enough free memory, the factory is used to procude one element which is then returned as memory handle. 39 * <p> 40 * You should invoke {@link MemoryHandle#free()} if you do not need that handle any more. 41 * @param <T> The content type of the memory- 42 * @param name A name for the memory area. Only used for debugging. 43 * @param maxBytes The maximum amount of bytes the content may have 44 * @param factory The factory to use to procude the content if there is sufficient memory. 45 * @return A memory handle to the content. 46 * @throws NotEnoughMemoryException If there is not enough memory to allocate. 47 */ 48 public synchronized <T> MemoryHandle<T> allocateMemory(String name, long maxBytes, Supplier<T> factory) throws NotEnoughMemoryException { 49 if (isAvailable(maxBytes)) { 50 T content = factory.get(); 51 if (content == null) { 52 throw new IllegalArgumentException("Factory did not return a content element."); 53 } 54 Main.info(MessageFormat.format("Allocate for {0}: {1} MB of memory. Available: {2} MB.", 55 name, maxBytes / 1024 / 1024, getAvailableMemory() / 1024 / 1024)); 56 MemoryHandle<T> handle = new ManualFreeMemoryHandle<>(name, content, maxBytes); 57 activeHandles.add(handle); 58 return handle; 59 } else { 60 throw new NotEnoughMemoryException(maxBytes); 61 } 62 } 63 64 /** 65 * Check if that memory is available 66 * @param maxBytes The memory to check for 67 * @return <code>true</code> if that memory is available. 68 */ 69 public synchronized boolean isAvailable(long maxBytes) { 70 if (maxBytes < 0) { 71 throw new IllegalArgumentException(MessageFormat.format("Cannot allocate negative number of bytes: {0]", maxBytes)); 72 } 73 return getAvailableMemory() >= maxBytes; 74 } 75 76 /** 77 * Gets the maximum amount of memory available for use in this manager. 78 * @return The maximum amount of memory. 79 */ 80 public synchronized long getMaxMemory() { 81 return Runtime.getRuntime().maxMemory() - JOSM_CORE_FOOTPRINT; 82 } 83 84 /** 85 * Gets the memory that is considered free. 86 * @return The memory that can be used for new allocations. 87 */ 88 public synchronized long getAvailableMemory() { 89 return getMaxMemory() - activeHandles.stream().mapToLong(h -> h.getSize()).sum(); 90 } 91 92 /** 93 * Get the global memory manager instance. 94 * @return The memory manager. 95 */ 96 public static MemoryManager getInstance() { 97 return INSTANCE; 98 } 99 100 /** 101 * Reset the state of this manager to the default state. 102 * @return true if there were entries that have been reset. 103 */ 104 protected synchronized List<MemoryHandle<?>> resetState() { 105 ArrayList<MemoryHandle<?>> toFree = new ArrayList<>(activeHandles); 106 toFree.stream().forEach(h -> h.free()); 107 return toFree; 108 } 109 110 /** 111 * A memory area managed by the {@link MemoryManager}. 112 * @author Michael Zangl 113 * @param <T> The content type. 114 * @since xxx 115 */ 116 public interface MemoryHandle<T> { 117 118 /** 119 * Gets the content of this memory area. 120 * <p> 121 * This method should be the prefered access to the memory since it will do error checking when {@link #free()} was called. 122 * @return The memory area content. 123 */ 124 public T get(); 125 126 /** 127 * Get the size that was requested for this memory area. 128 * @return the size 129 */ 130 public long getSize(); 131 132 /** 133 * Manually release this memory area. There should be no memory consumed by this afterwards. 134 */ 135 public void free(); 136 } 137 138 private class ManualFreeMemoryHandle<T> implements MemoryHandle<T> { 139 private final String name; 140 private T content; 141 private final long size; 142 143 ManualFreeMemoryHandle(String name, T content, long size) { 144 this.name = name; 145 this.content = content; 146 this.size = size; 147 } 148 149 @Override 150 public T get() { 151 if (content == null) { 152 throw new IllegalStateException(MessageFormat.format("Memory area was accessed after free(): {0}", name)); 153 } 154 return content; 155 } 156 157 @Override 158 public long getSize() { 159 return size; 160 } 161 162 @Override 163 public void free() { 164 if (content == null) { 165 throw new IllegalStateException(MessageFormat.format("Memory area was already marked as freed: {0}", name)); 166 } 167 content = null; 168 synchronized (MemoryManager.this) { 169 activeHandles.remove(this); 170 } 171 } 172 173 @Override 174 public String toString() { 175 return "MemoryHandle [name=" + name + ", size=" + size + "]"; 176 } 177 } 178 179 /** 180 * This exception is thrown if there is not enough memory for allocating the given object. 181 * @author Michael Zangl 182 * @since xxx 183 */ 184 public static class NotEnoughMemoryException extends Exception { 185 NotEnoughMemoryException(long memoryBytesRequired) { 186 super(tr("To add another layer you need to allocate at least {0,number,#}MB memory to JOSM using -Xmx{0,number,#}M " 187 + "option (see http://forum.openstreetmap.org/viewtopic.php?id=25677).\n" 188 + "Currently you have {1,number,#}MB memory allocated for JOSM", 189 memoryBytesRequired / 1024 / 1024, Runtime.getRuntime().maxMemory() / 1024 / 1024)); 190 } 191 } 192 } -
test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
diff --git a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java index 15aa764..5f3cf82 100644
a b import org.openstreetmap.josm.io.OsmApi; 18 18 import org.openstreetmap.josm.io.OsmApiInitializationException; 19 19 import org.openstreetmap.josm.io.OsmTransferCanceledException; 20 20 import org.openstreetmap.josm.tools.I18n; 21 import org.openstreetmap.josm.tools.MemoryManagerTest; 21 22 import org.openstreetmap.josm.tools.date.DateUtils; 22 23 23 24 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; … … public class JOSMTestRules implements TestRule { 38 39 private String i18n = null; 39 40 private boolean platform; 40 41 private boolean useProjection; 42 private boolean allowMemoryManagerLeaks; 41 43 42 44 /** 43 45 * Disable the default timeout for this test. Use with care. … … public class JOSMTestRules implements TestRule { 132 134 return this; 133 135 } 134 136 137 /** 138 * Allow the memory manager to contain items after execution of the test cases. 139 * @return this instance, for easy chaining 140 */ 141 public JOSMTestRules memoryManagerLeaks() { 142 allowMemoryManagerLeaks = true; 143 return this; 144 } 145 135 146 @Override 136 147 public Statement apply(Statement base, Description description) { 137 148 Statement statement = base; … … public class JOSMTestRules implements TestRule { 217 228 */ 218 229 @SuppressFBWarnings("DM_GC") 219 230 private void cleanUpFromJosmFixture() { 231 MemoryManagerTest.resetState(true); 220 232 Main.getLayerManager().resetState(); 221 233 Main.pref = null; 222 234 Main.platform = null; … … public class JOSMTestRules implements TestRule { 236 248 }); 237 249 // Remove all layers 238 250 Main.getLayerManager().resetState(); 251 MemoryManagerTest.resetState(allowMemoryManagerLeaks); 239 252 240 253 // TODO: Remove global listeners and other global state. 241 254 Main.pref = null; -
new file test/unit/org/openstreetmap/josm/tools/MemoryManagerTest.java
diff --git a/test/unit/org/openstreetmap/josm/tools/MemoryManagerTest.java b/test/unit/org/openstreetmap/josm/tools/MemoryManagerTest.java new file mode 100644 index 0000000..bcb0bde
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.tools; 3 4 import static org.junit.Assert.assertEquals; 5 import static org.junit.Assert.assertFalse; 6 import static org.junit.Assert.assertSame; 7 import static org.junit.Assert.assertTrue; 8 import static org.junit.Assert.fail; 9 10 import java.util.List; 11 12 import org.junit.Rule; 13 import org.junit.Test; 14 import org.openstreetmap.josm.testutils.JOSMTestRules; 15 import org.openstreetmap.josm.tools.MemoryManager.MemoryHandle; 16 import org.openstreetmap.josm.tools.MemoryManager.NotEnoughMemoryException; 17 18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 19 20 /** 21 * Tests the {@link MemoryManager} class. 22 * @author Michael Zangl 23 * @since xxx 24 */ 25 public class MemoryManagerTest { 26 /** 27 * Base test environment 28 */ 29 @Rule 30 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 31 public JOSMTestRules test = new JOSMTestRules().memoryManagerLeaks(); 32 33 /** 34 * Test {@link MemoryManager#allocateMemory(String, long, java.util.function.Supplier)} 35 * @throws NotEnoughMemoryException 36 */ 37 @Test 38 public void testUseMemory() throws NotEnoughMemoryException { 39 MemoryManager manager = MemoryManager.getInstance(); 40 long available = manager.getAvailableMemory(); 41 assertTrue(available < Runtime.getRuntime().maxMemory()); 42 assertEquals(available, manager.getMaxMemory()); 43 44 Object o1 = new Object(); 45 MemoryHandle<Object> testMemory = manager.allocateMemory("test", 10, () -> o1); 46 assertEquals(available - 10, manager.getAvailableMemory()); 47 assertSame(o1, testMemory.get()); 48 assertEquals(10, testMemory.getSize()); 49 assertTrue(testMemory.toString().startsWith("MemoryHandle")); 50 51 manager.allocateMemory("test2", 10, () -> new Object()); 52 assertEquals(available - 20, manager.getAvailableMemory()); 53 54 testMemory.free(); 55 assertEquals(available - 10, manager.getAvailableMemory()); 56 } 57 58 /** 59 * Test that {@link MemoryHandle#get()} checks for use after free. 60 * @throws NotEnoughMemoryException 61 */ 62 @Test(expected = IllegalStateException.class) 63 public void testUseAfterFree() throws NotEnoughMemoryException { 64 MemoryManager manager = MemoryManager.getInstance(); 65 MemoryHandle<Object> testMemory = manager.allocateMemory("test", 10, () -> new Object()); 66 testMemory.free(); 67 testMemory.get(); 68 } 69 70 /** 71 * Test that {@link MemoryHandle#get()} checks for free after free. 72 * @throws NotEnoughMemoryException 73 */ 74 @Test(expected = IllegalStateException.class) 75 public void testFreeAfterFree() throws NotEnoughMemoryException { 76 MemoryManager manager = MemoryManager.getInstance(); 77 MemoryHandle<Object> testMemory = manager.allocateMemory("test", 10, () -> new Object()); 78 testMemory.free(); 79 testMemory.free(); 80 } 81 /** 82 * Test that too big allocations fail 83 * @throws NotEnoughMemoryException 84 */ 85 @Test(expected = NotEnoughMemoryException.class) 86 public void testAllocationFails() throws NotEnoughMemoryException { 87 MemoryManager manager = MemoryManager.getInstance(); 88 long available = manager.getAvailableMemory(); 89 90 manager.allocateMemory("test", available + 1, () -> { 91 fail("Should not reach"); 92 return null; 93 }); 94 } 95 96 /** 97 * Test that allocations with null object fail 98 * @throws NotEnoughMemoryException 99 */ 100 @Test(expected = IllegalArgumentException.class) 101 public void testSupplierFails() throws NotEnoughMemoryException { 102 MemoryManager manager = MemoryManager.getInstance(); 103 104 manager.allocateMemory("test", 1, () -> null); 105 } 106 107 /** 108 * Test {@link MemoryManager#isAvailable(long)} 109 */ 110 @Test 111 public void testIsAvailable() { 112 MemoryManager manager = MemoryManager.getInstance(); 113 assertTrue(manager.isAvailable(10)); 114 assertTrue(manager.isAvailable(100)); 115 assertTrue(manager.isAvailable(10)); 116 } 117 118 /** 119 * Test {@link MemoryManager#isAvailable(long)} for negative number 120 * @throws NotEnoughMemoryException 121 */ 122 @Test(expected = IllegalArgumentException.class) 123 public void testIsAvailableFails() throws NotEnoughMemoryException { 124 MemoryManager manager = MemoryManager.getInstance(); 125 126 manager.isAvailable(-10); 127 } 128 129 /** 130 * Test {@link MemoryManager#resetState()} 131 * @throws NotEnoughMemoryException 132 */ 133 @Test 134 public void testResetState() throws NotEnoughMemoryException { 135 MemoryManager manager = MemoryManager.getInstance(); 136 assertTrue(manager.resetState().isEmpty()); 137 138 manager.allocateMemory("test", 10, () -> new Object()); 139 manager.allocateMemory("test2", 10, () -> new Object()); 140 assertEquals(2, manager.resetState().size()); 141 142 assertTrue(manager.resetState().isEmpty()); 143 } 144 145 /** 146 * Test {@link MemoryManager#resetState()} 147 * @throws NotEnoughMemoryException 148 */ 149 @Test(expected = IllegalStateException.class) 150 public void testResetStateUseAfterFree() throws NotEnoughMemoryException { 151 MemoryManager manager = MemoryManager.getInstance(); 152 MemoryHandle<Object> testMemory = manager.allocateMemory("test", 10, () -> new Object()); 153 154 assertFalse(manager.resetState().isEmpty()); 155 testMemory.get(); 156 } 157 158 /** 159 * Reset the state of the memory manager 160 * @param allowMemoryManagerLeaks If this is set, no exception is thrown if there were leaking entries. 161 */ 162 public static void resetState(boolean allowMemoryManagerLeaks) { 163 List<MemoryHandle<?>> hadLeaks = MemoryManager.getInstance().resetState(); 164 if (!allowMemoryManagerLeaks) { 165 assertTrue("Memory manager had leaking memory: " + hadLeaks, hadLeaks.isEmpty()); 166 } 167 } 168 }
