Permalink
Browse files

SM-2148: NPE in TimeoutMemoryStore under heavy load

git-svn-id: https://svn.apache.org/repos/asf/servicemix/utils/trunk@1302379 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information...
1 parent bcb6bee commit 81c96e16a4f2b90240e18962569f9c8659e6fd31 @gertv gertv committed Mar 19, 2012
View
19 src/main/java/org/apache/servicemix/store/memory/TimeoutMemoryStore.java
@@ -17,6 +17,7 @@
package org.apache.servicemix.store.memory;
import java.io.IOException;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -59,24 +60,26 @@ public Object load(String id) throws IOException {
evict();
LOG.debug("Loading object with id:" + id);
Entry entry = datas.remove(id);
- if(entry != null) {
+ if (entry != null) {
Object data = entry.getData();
fireRemovedEvent(id,data);
return data;
} else return null;
}
+ /*
+ * Remove timed out entries from the data map.
+ */
private void evict() {
long now = System.currentTimeMillis();
- for (String key : datas.keySet()) {
- long age = now - datas.get(key).getTime();
+
+ for (Map.Entry<String, Entry> entry : datas.entrySet()) {
+ long age = now - entry.getValue().getTime();
if (age > timeout) {
- LOG.debug("Removing object with id " + key + " from store after " + age + " ms");
- Entry entry = datas.get(key);
- if(entry != null) {
- fireEvictedEvent(key,entry.getData());
+ LOG.debug("Removing object with id " + entry.getKey() + " from store after " + age + " ms");
+ if(datas.remove(entry.getKey()) != null) {
+ fireEvictedEvent(entry.getKey(), entry.getValue().getData());
}
- datas.remove(key);
}
}
}
View
69 src/test/java/org/apache/servicemix/store/memory/ConcurrentTimeoutMemoryStoreTest.java
@@ -0,0 +1,69 @@
+package org.apache.servicemix.store.memory;
+
+import org.apache.servicemix.executors.Executor;
+import org.apache.servicemix.executors.ExecutorFactory;
+import org.apache.servicemix.executors.impl.ExecutorFactoryImpl;
+import org.apache.servicemix.id.IdGenerator;
+import org.apache.servicemix.store.Store;
+import org.junit.Test;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test case to ensure the {@link TimeoutMemoryStore} behaves properly under multi-threaded l
+ */
+public class ConcurrentTimeoutMemoryStoreTest {
+
+ private static final int NUMBER_OF_EXECUTORS = 5;
+ private static final int NUMBER_OF_OPERATIONS = 1000;
+ private static final Random RANDOM = new Random();
+ private static final int RANDOMNESS = 5;
+
+ private final ExecutorFactory factory = new ExecutorFactoryImpl();
+ private final Store store = new TimeoutMemoryStore(new IdGenerator(), 100);
+
+ @Test
+ public void testConcurrentLoadAndStore() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(NUMBER_OF_EXECUTORS * NUMBER_OF_OPERATIONS);
+
+ final Executor executor = factory.createExecutor("concurrent.timemout.memory.store");
+ for (int i = 0 ; i < 5 ; i++) {
+ executor.execute(new RandomAccessExecutable(store, latch));
+ }
+
+ assertTrue("We should have processed all operations successfully", latch.await(3, TimeUnit.SECONDS));
+ }
+
+ /**
+ * {@link Runnable} that randomly stores and removes items from a {@link Store} implementation
+ */
+ private class RandomAccessExecutable implements Runnable {
+
+ private final Store store;
+ private final CountDownLatch latch;
+
+ public RandomAccessExecutable(Store store, CountDownLatch latch) {
+ super();
+ this.store = store;
+ this.latch = latch;
+ }
+
+ public void run() {
+ for (int i = 0 ; i < NUMBER_OF_OPERATIONS ; i++) {
+ try {
+ store.store("Item " + RANDOM.nextInt(RANDOMNESS));
+ store.load("Item " + RANDOM.nextInt(RANDOMNESS));
+ latch.countDown();
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Unexception exception caught: " + e);
+ }
+ }
+ }
+ }
+}

0 comments on commit 81c96e1

Please sign in to comment.