You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by dr...@apache.org on 2017/05/08 09:00:23 UTC

[4/5] brooklyn-server git commit: Hide DataGrid inside BrooklynStorageImpl

Hide DataGrid inside BrooklynStorageImpl

Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/6b59fcb6
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/6b59fcb6
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/6b59fcb6

Branch: refs/heads/master
Commit: 6b59fcb6a0c8e57dcdd00b8c95ea2b410c8fcf2d
Parents: 21c40ba
Author: Aled Sage <al...@gmail.com>
Authored: Thu May 4 11:40:03 2017 +0100
Committer: Aled Sage <al...@gmail.com>
Committed: Fri May 5 11:23:52 2017 +0100

----------------------------------------------------------------------
 .../core/internal/storage/DataGrid.java         | 52 -----------
 .../storage/impl/BrooklynStorageImpl.java       | 18 +---
 .../internal/storage/impl/InmemoryDatagrid.java | 86 ++++++++++++++++++
 .../storage/impl/inmemory/InmemoryDatagrid.java | 93 --------------------
 .../internal/AbstractManagementContext.java     |  3 +-
 .../storage/impl/BrooklynStorageImplTest.java   |  6 +-
 .../longevity/EntityCleanupLongevityTest.java   |  2 +-
 .../EntityCleanupLongevityTestFixture.java      | 48 +++++-----
 8 files changed, 120 insertions(+), 188 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/main/java/org/apache/brooklyn/core/internal/storage/DataGrid.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/internal/storage/DataGrid.java b/core/src/main/java/org/apache/brooklyn/core/internal/storage/DataGrid.java
deleted file mode 100644
index 09f50fb..0000000
--- a/core/src/main/java/org/apache/brooklyn/core/internal/storage/DataGrid.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.core.internal.storage;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-
-import com.google.common.annotations.VisibleForTesting;
-
-public interface DataGrid {
-
-    /**
-     * If a map already exists with this id, returns it; otherwise creates a new map stored
-     * in the datagrid.
-     */
-    <K,V> ConcurrentMap<K,V> getMap(String id);
-
-    /**
-     * Deletes the map for this id, if it exists; otherwise a no-op.
-     */
-    void remove(String id);
-
-    /**
-     * Terminates the DataGrid. If there is a real datagrid with multiple machines running, it doesn't mean that the
-     * datagrid is going to be terminated; it only means that all local resources of the datagrid are released.
-     */
-    void terminate();
-    
-    Map<String, Object> getDatagridMetrics();
-
-    /** Returns snapshot of known keys at this datagrid */
-    @VisibleForTesting
-    Set<String> getKeys();
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImpl.java b/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImpl.java
index 60a1e7e..5436b15 100644
--- a/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImpl.java
+++ b/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImpl.java
@@ -25,40 +25,28 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
-import org.apache.brooklyn.core.internal.storage.DataGrid;
 import org.apache.brooklyn.core.internal.storage.Reference;
 
-import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 
 public class BrooklynStorageImpl implements BrooklynStorage {
 
-    private final DataGrid datagrid;
+    private final InmemoryDatagrid datagrid;
     private final ConcurrentMap<String, Object> refsMap;
     private final ConcurrentMap<String, Object> listsMap;
     private final ConcurrentMap<String, WeakReference<Reference<?>>> refsCache;
     private final ConcurrentMap<String, WeakReference<Reference<?>>> listRefsCache;
     
-    public BrooklynStorageImpl(DataGrid datagrid) {
-        this.datagrid = datagrid;
+    public BrooklynStorageImpl() {
+        this.datagrid = new InmemoryDatagrid();
         this.refsMap = datagrid.getMap("refs");
         this.listsMap = datagrid.getMap("lists");
         this.refsCache = Maps.newConcurrentMap();
         this.listRefsCache = Maps.newConcurrentMap();
     }
 
-    /**
-     * Returns the DataGrid used by this  BrooklynStorageImpl
-     *
-     * @return the DataGrid.
-     */
-    @VisibleForTesting
-    public DataGrid getDataGrid() {
-        return datagrid;
-    }
-
     @Override
     public <T> Reference<T> getReference(final String id) {
         // Can use different ref instances; no need to always return same one. Caching is an

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/InmemoryDatagrid.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/InmemoryDatagrid.java b/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/InmemoryDatagrid.java
new file mode 100644
index 0000000..a63fa3d
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/InmemoryDatagrid.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.brooklyn.core.internal.storage.impl;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.brooklyn.core.internal.storage.impl.ConcurrentMapAcceptingNullVals;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+/**
+ * A simple implementation of datagrid backed by in-memory (unpersisted) maps, within a single JVM.
+ * 
+ * @author aled
+ */
+class InmemoryDatagrid {
+
+    private final Map<String,Map<?,?>> maps = Maps.newLinkedHashMap();
+    private final AtomicInteger creationCounter = new AtomicInteger();
+    
+    @SuppressWarnings("unchecked")
+    public <K, V> ConcurrentMap<K, V> getMap(String id) {
+        synchronized (maps) {
+            ConcurrentMap<K, V> result = (ConcurrentMap<K, V>) maps.get(id);
+            if (result == null) {
+                result = newMap();
+                maps.put(id, result);
+                creationCounter.incrementAndGet();
+            }
+            return result;
+        }
+    }
+    
+    // TODO Not doing Maps.newConcurrentMap() because needs to store null values.
+    // Easy to avoid for Refererence<?> but harder for entity ConfigMap where the user
+    // can insert null values.
+    // 
+    // Could write a decorator that switches null values for a null marker, and back again.
+    //
+    private <K,V> ConcurrentMap<K,V> newMap() {
+        //return Collections.synchronizedMap(new HashMap<K, V>());
+        return new ConcurrentMapAcceptingNullVals<K,V>(Maps.<K,V>newConcurrentMap());
+    }
+
+    public void remove(String id) {
+        synchronized (maps) {
+            maps.remove(id);
+        }
+    }
+
+    public void terminate() {
+        synchronized (maps) {
+            maps.clear();
+        }
+    }
+
+    public Map<String, Object> getDatagridMetrics() {
+        synchronized (maps) {
+            return ImmutableMap.<String, Object>of("size", maps.size(), "createCount", creationCounter.get());
+        }
+    }
+
+    public Set<String> getKeys() {
+        return maps.keySet();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/inmemory/InmemoryDatagrid.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/inmemory/InmemoryDatagrid.java b/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/inmemory/InmemoryDatagrid.java
deleted file mode 100644
index 48600d9..0000000
--- a/core/src/main/java/org/apache/brooklyn/core/internal/storage/impl/inmemory/InmemoryDatagrid.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.brooklyn.core.internal.storage.impl.inmemory;
-
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.brooklyn.core.internal.storage.DataGrid;
-import org.apache.brooklyn.core.internal.storage.impl.ConcurrentMapAcceptingNullVals;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-
-/**
- * A simple implementation of datagrid backed by in-memory (unpersisted) maps, within a single JVM.
- * 
- * @author aled
- */
-public class InmemoryDatagrid implements DataGrid {
-
-    private final Map<String,Map<?,?>> maps = Maps.newLinkedHashMap();
-    private final AtomicInteger creationCounter = new AtomicInteger();
-    
-    @SuppressWarnings("unchecked")
-    @Override
-    public <K, V> ConcurrentMap<K, V> getMap(String id) {
-        synchronized (maps) {
-            ConcurrentMap<K, V> result = (ConcurrentMap<K, V>) maps.get(id);
-            if (result == null) {
-                result = newMap();
-                maps.put(id, result);
-                creationCounter.incrementAndGet();
-            }
-            return result;
-        }
-    }
-    
-    // TODO Not doing Maps.newConcurrentMap() because needs to store null values.
-    // Easy to avoid for Refererence<?> but harder for entity ConfigMap where the user
-    // can insert null values.
-    // 
-    // Could write a decorator that switches null values for a null marker, and back again.
-    //
-    private <K,V> ConcurrentMap<K,V> newMap() {
-        //return Collections.synchronizedMap(new HashMap<K, V>());
-        return new ConcurrentMapAcceptingNullVals<K,V>(Maps.<K,V>newConcurrentMap());
-    }
-
-    @Override
-    public void remove(String id) {
-        synchronized (maps) {
-            maps.remove(id);
-        }
-    }
-
-    @Override
-    public void terminate() {
-        synchronized (maps) {
-            maps.clear();
-        }
-    }
-
-    @Override
-    public Map<String, Object> getDatagridMetrics() {
-        synchronized (maps) {
-            return ImmutableMap.<String, Object>of("size", maps.size(), "createCount", creationCounter.get());
-        }
-    }
-
-    @Override
-    public Set<String> getKeys() {
-        return maps.keySet();
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
index 45a45d7..c009437 100644
--- a/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
+++ b/core/src/main/java/org/apache/brooklyn/core/mgmt/internal/AbstractManagementContext.java
@@ -61,7 +61,6 @@ import org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadsManager;
 import org.apache.brooklyn.core.internal.BrooklynProperties;
 import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
 import org.apache.brooklyn.core.internal.storage.impl.BrooklynStorageImpl;
-import org.apache.brooklyn.core.internal.storage.impl.inmemory.InmemoryDatagrid;
 import org.apache.brooklyn.core.location.BasicLocationRegistry;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
 import org.apache.brooklyn.core.mgmt.classloading.BrooklynClassLoadingContextSequential;
@@ -169,7 +168,7 @@ public abstract class AbstractManagementContext implements ManagementContextInte
         this.catalog = new BasicBrooklynCatalog(this);
         this.typeRegistry = new BasicBrooklynTypeRegistry(this);
         
-        this.storage = new BrooklynStorageImpl(new InmemoryDatagrid());
+        this.storage = new BrooklynStorageImpl();
         this.rebindManager = new RebindManagerImpl(this); // TODO leaking "this" reference; yuck
         this.highAvailabilityManager = new HighAvailabilityManagerImpl(this); // TODO leaking "this" reference; yuck
         

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/test/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImplTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImplTest.java b/core/src/test/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImplTest.java
index 9dc02c5..c90a206 100644
--- a/core/src/test/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImplTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/internal/storage/impl/BrooklynStorageImplTest.java
@@ -29,10 +29,8 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
-import org.apache.brooklyn.core.internal.storage.DataGrid;
 import org.apache.brooklyn.core.internal.storage.Reference;
 import org.apache.brooklyn.core.internal.storage.impl.BrooklynStorageImpl;
-import org.apache.brooklyn.core.internal.storage.impl.inmemory.InmemoryDatagrid;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -43,15 +41,13 @@ import com.google.common.collect.Lists;
 
 public class BrooklynStorageImplTest {
     
-    private DataGrid datagrid;
     private BrooklynStorage storage;
 
     @BeforeMethod(alwaysRun=true)
     public void setUp() throws Exception {
         // TODO Note that InmemoryDatagrid's ConcurrentMap currently returns snapshot for entrySet() and values()
         // so the tests here aren't particularly good for confirming it'll work against a real datagrid...
-        datagrid = new InmemoryDatagrid();
-        storage = new BrooklynStorageImpl(datagrid);
+        storage = new BrooklynStorageImpl();
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTest.java b/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTest.java
index 26a45fa..bdc56cf 100644
--- a/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTest.java
+++ b/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTest.java
@@ -53,7 +53,7 @@ public class EntityCleanupLongevityTest extends EntityCleanupLongevityTestFixtur
         doTestManyTimesAndAssertNoMemoryLeak(JavaClassNames.niceClassAndMethod(), new Runnable() {
             @Override
             public void run() {
-                loc = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
+                loc = newLoc();
                 managementContext.getLocationManager().unmanage(loc);
             }
         });

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6b59fcb6/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTestFixture.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTestFixture.java b/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTestFixture.java
index 8eeb29e..e8544d8 100644
--- a/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTestFixture.java
+++ b/core/src/test/java/org/apache/brooklyn/core/test/qa/longevity/EntityCleanupLongevityTestFixture.java
@@ -18,7 +18,9 @@
  */
 package org.apache.brooklyn.core.test.qa.longevity;
 
-import java.util.Set;
+import static org.testng.Assert.assertTrue;
+
+import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 
@@ -29,8 +31,6 @@ import org.apache.brooklyn.api.sensor.SensorEventListener;
 import org.apache.brooklyn.core.entity.Entities;
 import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
 import org.apache.brooklyn.core.internal.storage.BrooklynStorage;
-import org.apache.brooklyn.core.internal.storage.DataGrid;
-import org.apache.brooklyn.core.internal.storage.impl.BrooklynStorageImpl;
 import org.apache.brooklyn.core.location.SimulatedLocation;
 import org.apache.brooklyn.core.mgmt.internal.AbstractManagementContext;
 import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
@@ -50,6 +50,7 @@ import org.testng.annotations.BeforeMethod;
 
 import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 
 public abstract class EntityCleanupLongevityTestFixture {
 
@@ -59,6 +60,9 @@ public abstract class EntityCleanupLongevityTestFixture {
     protected SimulatedLocation loc;
     protected TestApplication app;
 
+    protected WeakHashMap<TestApplication, Void> weakApps;
+    protected WeakHashMap<SimulatedLocation, Void> weakLocs;
+    
     // since GC is not definitive (would that it were!)
     final static long MEMORY_MARGIN_OF_ERROR = 10*1024*1024;
 
@@ -76,6 +80,9 @@ public abstract class EntityCleanupLongevityTestFixture {
         
         // do this to ensure GC is initialized
         managementContext.getExecutionManager();
+        
+        weakApps = new WeakHashMap<>();
+        weakLocs = new WeakHashMap<>();
     }
     
     @AfterMethod(alwaysRun=true)
@@ -98,7 +105,8 @@ public abstract class EntityCleanupLongevityTestFixture {
                 long now = timer.elapsed(TimeUnit.MILLISECONDS);
                 System.gc(); System.gc();
                 String msg = testName+" iteration " + i + " at " + Time.makeTimeStringRounded(now) + " (delta "+Time.makeTimeStringRounded(now-last)+"), using "+
-                    ((AbstractManagementContext)managementContext).getGarbageCollector().getUsageString();
+                    ((AbstractManagementContext)managementContext).getGarbageCollector().getUsageString()+
+                    "; weak-refs app="+Iterables.size(weakApps.keySet())+" and locs="+Iterables.size(weakLocs.keySet());
                 LOG.info(msg);
                 if (i>=100 && memUsedNearStart<0) {
                     // set this the first time we've run 100 times (let that create a baseline with classes loaded etc)
@@ -112,20 +120,6 @@ public abstract class EntityCleanupLongevityTestFixture {
         BrooklynStorage storage = ((ManagementContextInternal)managementContext).getStorage();
         Assert.assertTrue(storage.isMostlyEmpty(), "Not empty storage: "+storage);
         
-        DataGrid dg = ((BrooklynStorageImpl)storage).getDataGrid();
-        Set<String> keys = dg.getKeys();
-        for (String key: keys) {
-            ConcurrentMap<Object, Object> v = dg.getMap(key);
-            if (v.isEmpty()) continue;
-            // TODO currently we remember ApplicationUsage
-            if (key.contains("usage-application")) {
-                Assert.assertTrue(v.size() <= iterations, "Too many usage-application entries: "+v.size());
-                continue;
-            }
-            
-            Assert.fail("Non-empty key in datagrid: "+key+" ("+v+")");
-        }
-
         ConcurrentMap<Object, TaskScheduler> schedulers = ((BasicExecutionManager)managementContext.getExecutionManager()).getSchedulerByTag();
         // TODO would like to assert this
 //        Assert.assertTrue( schedulers.isEmpty(), "Not empty schedulers: "+schedulers);
@@ -134,14 +128,21 @@ public abstract class EntityCleanupLongevityTestFixture {
         
         // memory leak detection only applies to subclasses who run lots of iterations
         if (checkMemoryLeaks())
-            assertNoMemoryLeak(memUsedNearStart);
+            assertNoMemoryLeak(memUsedNearStart, iterations);
     }
 
-    protected void assertNoMemoryLeak(long memUsedPreviously) {
+    protected void assertNoMemoryLeak(long memUsedPreviously, int iterations) {
         System.gc(); System.gc();
         long memUsedAfter = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
         long memChange = memUsedAfter - memUsedPreviously;
         Assert.assertTrue(memChange < numIterations()*ACCEPTABLE_LEAK_PER_ITERATION + MEMORY_MARGIN_OF_ERROR, "Leaked too much memory: "+Strings.makeJavaSizeString(memChange));
+        
+        // TODO Want a stronger assertion than this - it just says we don't have more apps than we created! 
+        int numApps = Iterables.size(weakApps.keySet());
+        assertTrue(numApps <= iterations, "numApps="+numApps+"; iterations="+iterations);
+        
+        int numLocs = Iterables.size(weakLocs.keySet());
+        assertTrue(numLocs <= iterations, "numLocs="+numLocs+"; iterations="+iterations);
     }
     
     protected void doTestStartAppThenThrowAway(String testName, final boolean stop) {
@@ -164,6 +165,7 @@ public abstract class EntityCleanupLongevityTestFixture {
 
     protected TestApplication newApp() {
         final TestApplication result = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
+        weakApps.put(app, null);
         TestEntity entity = result.createAndManageChild(EntitySpec.create(TestEntity.class));
         result.subscriptions().subscribe(entity, TestEntity.NAME, new SensorEventListener<String>() {
             @Override public void onEvent(SensorEvent<String> event) {
@@ -172,4 +174,10 @@ public abstract class EntityCleanupLongevityTestFixture {
         entity.sensors().set(TestEntity.NAME, "myname");
         return result;
     }
+    
+    protected SimulatedLocation newLoc() {
+        SimulatedLocation result = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class));
+        weakLocs.put(result, null);
+        return result;
+    }
 }