Author: manik.surtani(a)jboss.com
Date: 2009-02-08 06:20:49 -0500 (Sun, 08 Feb 2009)
New Revision: 7662
Added:
core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java
core/branches/flat/src/main/java/org/horizon/eviction/events/PurgedDataEndEvent.java
core/branches/flat/src/test/java/org/horizon/container/
core/branches/flat/src/test/java/org/horizon/container/DataContainerTest.java
core/branches/flat/src/test/java/org/horizon/expiry/
core/branches/flat/src/test/java/org/horizon/expiry/ExpiryTest.java
Removed:
core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java
core/branches/flat/src/test/java/org/horizon/BasicTest.java
core/branches/flat/src/test/java/org/horizon/SkipListTest.java
Modified:
core/branches/flat/src/main/java/org/horizon/Cache.java
core/branches/flat/src/main/java/org/horizon/CacheDelegate.java
core/branches/flat/src/main/java/org/horizon/commands/CommandsFactory.java
core/branches/flat/src/main/java/org/horizon/commands/CommandsFactoryImpl.java
core/branches/flat/src/main/java/org/horizon/commands/write/PutKeyValueCommand.java
core/branches/flat/src/main/java/org/horizon/commands/write/PutMapCommand.java
core/branches/flat/src/main/java/org/horizon/commands/write/ReplaceCommand.java
core/branches/flat/src/main/java/org/horizon/container/DataContainer.java
core/branches/flat/src/main/java/org/horizon/container/MVCCEntry.java
core/branches/flat/src/main/java/org/horizon/container/ReadCommittedEntry.java
core/branches/flat/src/main/java/org/horizon/container/RepeatableReadEntry.java
core/branches/flat/src/main/java/org/horizon/container/UnsortedDataContainer.java
core/branches/flat/src/main/java/org/horizon/eviction/EvictionAlgorithm.java
core/branches/flat/src/main/java/org/horizon/eviction/EvictionManagerImpl.java
core/branches/flat/src/main/java/org/horizon/eviction/algorithms/BaseEvictionAlgorithm.java
core/branches/flat/src/main/java/org/horizon/factories/EntryFactory.java
core/branches/flat/src/main/java/org/horizon/factories/EntryFactoryImpl.java
core/branches/flat/src/main/java/org/horizon/interceptors/CacheLoaderInterceptor.java
core/branches/flat/src/main/java/org/horizon/interceptors/LockingInterceptor.java
core/branches/flat/src/main/java/org/horizon/util/AbstractMap.java
core/branches/flat/src/main/java/org/horizon/util/FastCopyHashMap.java
core/branches/flat/src/test/java/org/horizon/api/CacheAPITest.java
Log:
Expiry stuff
Modified: core/branches/flat/src/main/java/org/horizon/Cache.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/Cache.java 2009-02-06 18:10:10 UTC (rev
7661)
+++ core/branches/flat/src/main/java/org/horizon/Cache.java 2009-02-08 11:20:49 UTC (rev
7662)
@@ -28,7 +28,9 @@
import org.horizon.manager.CacheManager;
import org.horizon.notifications.Listenable;
+import java.util.Map;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
/**
* @author Mircea.Markus(a)jboss.com
@@ -123,6 +125,65 @@
*/
void removeInterceptor(Class<? extends CommandInterceptor> interceptorType);
+ /**
+ * Retrieves the cache manager responsible for creating this cache instance.
+ *
+ * @return a cache manager
+ */
+ CacheManager getCacheManager();
- CacheManager getCacheManager();
+ /**
+ * An overloaded form of {@link #put(Object, Object)}, which takes in lifespan
parameters.
+ *
+ * @param key key to use
+ * @param value value to store
+ * @param lifespan lifespan of the entry. Negative values are intepreted as unlimited
lifespan.
+ * @param unit unit of measurement for the lifespan
+ * @return the value being replaced, or null if nothing is being replaced.
+ */
+ V put(K key, V value, long lifespan, TimeUnit unit);
+
+ /**
+ * An overloaded form of {@link #putIfAbsent(Object, Object)}, which takes in lifespan
parameters.
+ *
+ * @param key key to use
+ * @param value value to store
+ * @param lifespan lifespan of the entry. Negative values are intepreted as unlimited
lifespan.
+ * @param unit unit of measurement for the lifespan
+ * @return the value being replaced, or null if nothing is being replaced.
+ */
+ V putIfAbsent(K key, V value, long lifespan, TimeUnit unit);
+
+ /**
+ * An overloaded form of {@link #putAll(java.util.Map)}, which takes in lifespan
parameters. Note that the lifespan
+ * is applied to all mappings in the map passed in.
+ *
+ * @param map map containing mappings to enter
+ * @param lifespan lifespan of the entry. Negative values are intepreted as unlimited
lifespan.
+ * @param unit unit of measurement for the lifespan
+ */
+ void putAll(Map<? extends K, ? extends V> map, long lifespan, TimeUnit unit);
+
+ /**
+ * An overloaded form of {@link #replace(Object, Object)}, which takes in lifespan
parameters.
+ *
+ * @param key key to use
+ * @param value value to store
+ * @param lifespan lifespan of the entry. Negative values are intepreted as unlimited
lifespan.
+ * @param unit unit of measurement for the lifespan
+ * @return the value being replaced, or null if nothing is being replaced.
+ */
+ V replace(K key, V value, long lifespan, TimeUnit unit);
+
+ /**
+ * An overloaded form of {@link #replace(Object, Object, Object)}, which takes in
lifespan parameters.
+ *
+ * @param key key to use
+ * @param oldValue value to replace
+ * @param value value to store
+ * @param lifespan lifespan of the entry. Negative values are intepreted as unlimited
lifespan.
+ * @param unit unit of measurement for the lifespan
+ * @return true if the value was replaced, false otherwise
+ */
+ boolean replace(K key, V oldValue, V value, long lifespan, TimeUnit unit);
}
Modified: core/branches/flat/src/main/java/org/horizon/CacheDelegate.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/CacheDelegate.java 2009-02-06 18:10:10
UTC (rev 7661)
+++ core/branches/flat/src/main/java/org/horizon/CacheDelegate.java 2009-02-08 11:20:49
UTC (rev 7662)
@@ -63,6 +63,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* @author Mircea.Markus(a)jboss.com
@@ -396,4 +397,30 @@
public CacheManager getCacheManager() {
return cacheManager;
}
+
+ public V put(K key, V value, long lifespan, TimeUnit unit) {
+ PutKeyValueCommand command = commandsFactory.buildPutKeyValueCommand(key, value,
unit.toMillis(lifespan));
+ return (V) invoker.invoke(buildCtx(), command);
+ }
+
+ public V putIfAbsent(K key, V value, long lifespan, TimeUnit unit) {
+ PutKeyValueCommand command = commandsFactory.buildPutKeyValueCommand(key, value,
unit.toMillis(lifespan));
+ command.setPutIfAbsent(true);
+ return (V) invoker.invoke(buildCtx(), command);
+ }
+
+ public void putAll(Map<? extends K, ? extends V> map, long lifespan, TimeUnit
unit) {
+ PutMapCommand command = commandsFactory.buildPutMapCommand(map,
unit.toMillis(lifespan));
+ invoker.invoke(buildCtx(), command);
+ }
+
+ public V replace(K key, V value, long lifespan, TimeUnit unit) {
+ ReplaceCommand command = commandsFactory.buildReplaceCommand(key, null, value,
unit.toMillis(lifespan));
+ return (V) invoker.invoke(buildCtx(), command);
+ }
+
+ public boolean replace(K key, V oldValue, V value, long lifespan, TimeUnit unit) {
+ ReplaceCommand command = commandsFactory.buildReplaceCommand(key, oldValue, value,
unit.toMillis(lifespan));
+ return (Boolean) invoker.invoke(buildCtx(), command);
+ }
}
Modified: core/branches/flat/src/main/java/org/horizon/commands/CommandsFactory.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/commands/CommandsFactory.java 2009-02-06
18:10:10 UTC (rev 7661)
+++ core/branches/flat/src/main/java/org/horizon/commands/CommandsFactory.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -48,20 +48,27 @@
*/
@Scope(Scopes.NAMED_CACHE)
public interface CommandsFactory {
+
PutKeyValueCommand buildPutKeyValueCommand(Object key, Object value);
+ PutKeyValueCommand buildPutKeyValueCommand(Object key, Object value, long
lifespanMillis);
+
RemoveCommand buildRemoveCommand(Object key, Object value);
InvalidateCommand buildInvalidateCommand(Object... keys);
ReplaceCommand buildReplaceCommand(Object key, Object oldValue, Object newValue);
+ ReplaceCommand buildReplaceCommand(Object key, Object oldValue, Object newValue, long
lifespanMillis);
+
SizeCommand buildSizeCommand();
GetKeyValueCommand buildGetKeyValueCommand(Object key);
PutMapCommand buildPutMapCommand(Map t);
+ PutMapCommand buildPutMapCommand(Map t, long lifespanMillis);
+
ClearCommand buildClearCommand();
EvictCommand buildEvictCommand(Object key);
Modified: core/branches/flat/src/main/java/org/horizon/commands/CommandsFactoryImpl.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/commands/CommandsFactoryImpl.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/commands/CommandsFactoryImpl.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -67,6 +67,10 @@
return new PutKeyValueCommand(key, value, false, notifier);
}
+ public PutKeyValueCommand buildPutKeyValueCommand(Object key, Object value, long
lifespanMillis) {
+ return new PutKeyValueCommand(key, value, false, notifier, lifespanMillis);
+ }
+
public RemoveCommand buildRemoveCommand(Object key, Object value) {
return new RemoveCommand(key, value, notifier);
}
@@ -79,6 +83,10 @@
return new ReplaceCommand(key, oldValue, newValue);
}
+ public ReplaceCommand buildReplaceCommand(Object key, Object oldValue, Object
newValue, long lifespan) {
+ return new ReplaceCommand(key, oldValue, newValue, lifespan);
+ }
+
public SizeCommand buildSizeCommand() {
if (cachedSizeCommand == null) {
cachedSizeCommand = new SizeCommand(dataContainer);
@@ -94,6 +102,10 @@
return new PutMapCommand(map, notifier);
}
+ public PutMapCommand buildPutMapCommand(Map map, long lifespan) {
+ return new PutMapCommand(map, notifier, lifespan);
+ }
+
public ClearCommand buildClearCommand() {
return new ClearCommand();
}
Modified:
core/branches/flat/src/main/java/org/horizon/commands/write/PutKeyValueCommand.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/commands/write/PutKeyValueCommand.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/commands/write/PutKeyValueCommand.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -42,7 +42,16 @@
protected boolean putIfAbsent;
private CacheNotifier notifier;
boolean successful = true;
+ long lifespanMillis = -1;
+ public PutKeyValueCommand(Object key, Object value, boolean putIfAbsent, CacheNotifier
notifier, long lifespanMillis) {
+ super(key);
+ this.value = value;
+ this.putIfAbsent = putIfAbsent;
+ this.notifier = notifier;
+ this.lifespanMillis = lifespanMillis;
+ }
+
public PutKeyValueCommand(Object key, Object value, boolean putIfAbsent, CacheNotifier
notifier) {
super(key);
this.value = value;
@@ -99,13 +108,18 @@
}
public Object[] getParameters() {
- return new Object[]{key, value};
+ if (lifespanMillis < 0)
+ return new Object[]{key, value, false};
+ else
+ return new Object[]{key, value, true, lifespanMillis};
}
public void setParameters(int commandId, Object[] parameters) {
if (commandId != METHOD_ID) throw new IllegalStateException("Invalid method
id");
key = parameters[0];
value = parameters[1];
+ boolean setLifespan = (Boolean) parameters[2];
+ if (setLifespan) lifespanMillis = (Long) parameters[3];
}
public boolean isPutIfAbsent() {
@@ -116,31 +130,40 @@
this.putIfAbsent = putIfAbsent;
}
+ public long getLifespanMillis() {
+ return lifespanMillis;
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof PutKeyValueCommand)) return false;
+ if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
PutKeyValueCommand that = (PutKeyValueCommand) o;
+ if (lifespanMillis != that.lifespanMillis) return false;
if (putIfAbsent != that.putIfAbsent) return false;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return true;
}
+ @Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (putIfAbsent ? 1 : 0);
+ result = 31 * result + (int) (lifespanMillis ^ (lifespanMillis >>> 32));
return result;
}
+ @Override
public String toString() {
return "PutKeyValueCommand{" +
- "key= " + key +
+ "lifespanMillis=" + lifespanMillis +
+ ", putIfAbsent=" + putIfAbsent +
", value=" + value +
- ", putIfAbsent=" + putIfAbsent +
'}';
}
Modified: core/branches/flat/src/main/java/org/horizon/commands/write/PutMapCommand.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/commands/write/PutMapCommand.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/commands/write/PutMapCommand.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -38,7 +38,14 @@
private Map<Object, Object> map;
private CacheNotifier notifier;
+ private long lifespanMillis = -1;
+ public PutMapCommand(Map map, CacheNotifier notifier, long lifespanMillis) {
+ this.map = map;
+ this.notifier = notifier;
+ this.lifespanMillis = lifespanMillis;
+ }
+
public PutMapCommand(Map map, CacheNotifier notifier) {
this.map = map;
this.notifier = notifier;
@@ -79,37 +86,51 @@
}
public Object[] getParameters() {
- return new Object[]{map};
+ if (lifespanMillis < 0)
+ return new Object[]{map, false};
+ else
+ return new Object[]{map, true, lifespanMillis};
}
public void setParameters(int commandId, Object[] parameters) {
- if (commandId != METHOD_ID) throw new IllegalStateException("Invalid method
id");
map = (Map) parameters[0];
+ boolean setLifespan = (Boolean) parameters[1];
+ if (setLifespan) lifespanMillis = (Long) parameters[2];
}
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof PutMapCommand)) return false;
+ if (o == null || getClass() != o.getClass()) return false;
PutMapCommand that = (PutMapCommand) o;
+ if (lifespanMillis != that.lifespanMillis) return false;
if (map != null ? !map.equals(that.map) : that.map != null) return false;
return true;
}
+ @Override
public int hashCode() {
- return (map != null ? map.hashCode() : 0);
+ int result = map != null ? map.hashCode() : 0;
+ result = 31 * result + (int) (lifespanMillis ^ (lifespanMillis >>> 32));
+ return result;
}
-
+ @Override
public String toString() {
return "PutMapCommand{" +
"map=" + map +
+ ", lifespanMillis=" + lifespanMillis +
'}';
}
public boolean isSuccessful() {
return true;
}
+
+ public long getLifespanMillis() {
+ return lifespanMillis;
+ }
}
Modified: core/branches/flat/src/main/java/org/horizon/commands/write/ReplaceCommand.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/commands/write/ReplaceCommand.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/commands/write/ReplaceCommand.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -36,8 +36,16 @@
protected Object oldValue;
protected Object newValue;
+ protected long lifespanMillis = -1;
boolean successful = true;
+ public ReplaceCommand(Object key, Object oldValue, Object newValue, long
lifespanMillis) {
+ super(key);
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ this.lifespanMillis = lifespanMillis;
+ }
+
public ReplaceCommand(Object key, Object oldValue, Object newValue) {
super(key);
this.oldValue = oldValue;
@@ -86,7 +94,10 @@
}
public Object[] getParameters() {
- return new Object[]{key, oldValue, newValue};
+ if (lifespanMillis < 0)
+ return new Object[]{key, oldValue, newValue, false};
+ else
+ return new Object[]{key, oldValue, newValue, true, lifespanMillis};
}
public void setParameters(int commandId, Object[] parameters) {
@@ -94,37 +105,39 @@
key = parameters[0];
oldValue = parameters[1];
newValue = parameters[2];
+ boolean setLifespan = (Boolean) parameters[3];
+ if (setLifespan) lifespanMillis = (Long) parameters[4];
}
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
- if (!(o instanceof ReplaceCommand)) return false;
+ if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ReplaceCommand that = (ReplaceCommand) o;
+ if (lifespanMillis != that.lifespanMillis) return false;
if (newValue != null ? !newValue.equals(that.newValue) : that.newValue != null)
return false;
if (oldValue != null ? !oldValue.equals(that.oldValue) : that.oldValue != null)
return false;
return true;
}
+ @Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (oldValue != null ? oldValue.hashCode() : 0);
result = 31 * result + (newValue != null ? newValue.hashCode() : 0);
+ result = 31 * result + (int) (lifespanMillis ^ (lifespanMillis >>> 32));
return result;
}
- public String toString() {
- return "ReplaceCommand{" +
- "key=" + key +
- ", oldValue=" + oldValue +
- ", newValue=" + newValue +
- '}';
- }
-
public boolean isSuccessful() {
return successful;
}
+
+ public long getLifespanMillis() {
+ return lifespanMillis;
+ }
}
Modified: core/branches/flat/src/main/java/org/horizon/container/DataContainer.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/container/DataContainer.java 2009-02-06
18:10:10 UTC (rev 7661)
+++ core/branches/flat/src/main/java/org/horizon/container/DataContainer.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -27,7 +27,7 @@
import java.util.Set;
/**
- * // TODO: MANIK: Document this
+ * The main internal data structure which stores entries
*
* @author Manik Surtani (<a
href="mailto:manik@jboss.org">manik@jboss.org</a>)
* @since 1.0
@@ -36,7 +36,7 @@
public interface DataContainer<K, V> {
V get(K k);
- void put(K k, V v);
+ void put(K k, V v, long lifespan);
boolean containsKey(K k);
@@ -48,7 +48,12 @@
Set<K> keySet();
- long getCreatedTimestamp(K key);
+ long getModifiedTimestamp(K key);
- long getModifiedTimestamp(K key);
+ /**
+ * Purges entries that have passed their expiry time, returning a set of keys that
have been purged.
+ *
+ * @return set of keys that have been purged.
+ */
+ Set<K> purgeExpiredEntries();
}
Modified: core/branches/flat/src/main/java/org/horizon/container/MVCCEntry.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/container/MVCCEntry.java 2009-02-06
18:10:10 UTC (rev 7661)
+++ core/branches/flat/src/main/java/org/horizon/container/MVCCEntry.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -21,8 +21,6 @@
*/
package org.horizon.container;
-import org.horizon.context.InvocationContext;
-
import java.util.Map.Entry;
/**
@@ -36,7 +34,7 @@
void copyForUpdate(DataContainer container, boolean writeSkewCheck);
- void commitUpdate(InvocationContext ctx, DataContainer container);
+ void commitUpdate(DataContainer container);
void rollbackUpdate();
Modified: core/branches/flat/src/main/java/org/horizon/container/ReadCommittedEntry.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/container/ReadCommittedEntry.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/container/ReadCommittedEntry.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -22,7 +22,6 @@
package org.horizon.container;
import static org.horizon.container.ReadCommittedEntry.Flags.*;
-import org.horizon.context.InvocationContext;
import org.horizon.logging.Log;
import org.horizon.logging.LogFactory;
@@ -38,16 +37,18 @@
protected Object key, value, oldValue;
protected byte flags = 0;
+ private long lifespan;
protected ReadCommittedEntry() {
setValid(true);
}
- public ReadCommittedEntry(Object key, Object value) {
+ public ReadCommittedEntry(Object key, Object value, long lifespan) {
setValid(true);
this.key = key;
this.value = value;
+ this.lifespan = lifespan;
}
public Object getKey() {
@@ -115,16 +116,15 @@
}
@SuppressWarnings("unchecked")
- public void commitUpdate(InvocationContext ctx, DataContainer container) {
+ public void commitUpdate(DataContainer container) {
// only do stuff if there are changes.
if (isFlagSet(CHANGED)) {
if (trace)
- log.trace("Updating entry [" + getKey() + "]. deleted="
+ isDeleted() + " valid=" + isValid() + " changed=" + isChanged() +
" created=" + isFlagSet(CREATED));
+ log.trace("Updating entry [" + getKey() + "]. deleted="
+ isDeleted() + " valid=" + isValid() + " changed=" + isChanged() +
" created=" + isFlagSet(CREATED) + " value=" + value);
if (isFlagSet(DELETED)) {
container.remove(key);
-
} else {
- container.put(key, value);
+ container.put(key, value, lifespan);
}
reset();
}
Modified: core/branches/flat/src/main/java/org/horizon/container/RepeatableReadEntry.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/container/RepeatableReadEntry.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/container/RepeatableReadEntry.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -35,8 +35,8 @@
public class RepeatableReadEntry extends ReadCommittedEntry {
private static final Log log = LogFactory.getLog(RepeatableReadEntry.class);
- public RepeatableReadEntry(Object key, Object value) {
- super(key, value);
+ public RepeatableReadEntry(Object key, Object value, long lifespan) {
+ super(key, value, lifespan);
}
@Override
Modified:
core/branches/flat/src/main/java/org/horizon/container/UnsortedDataContainer.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/container/UnsortedDataContainer.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/container/UnsortedDataContainer.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -21,8 +21,16 @@
*/
package org.horizon.container;
+import org.horizon.factories.annotations.Inject;
+import org.horizon.loader.CacheLoader;
+import org.horizon.loader.CacheLoaderManager;
+
import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -34,9 +42,31 @@
* @since 1.0
*/
public class UnsortedDataContainer<K, V> implements DataContainer<K, V> {
- private final ConcurrentMap<Object, CachedValue<V>> data = new
ConcurrentHashMap<Object, CachedValue<V>>();
+ // data with expiry and without expiry are stored in different maps, for efficiency.
E.g., so that when purging expired
+ // stuff, we don't need to iterate through immortal data.
+ final ConcurrentMap<K, CachedValue<V>> immortalData = new
ConcurrentHashMap<K, CachedValue<V>>();
+ final ConcurrentMap<K, ExpirableCachedValue<V>> expirableData = new
ConcurrentHashMap<K, ExpirableCachedValue<V>>();
private static final Object NULL = new Object();
+ private CacheLoaderManager clm;
+ private CacheLoader cacheLoader;
+ @Inject
+ public void injectDependencies(CacheLoaderManager clm) {
+ this.clm = clm;
+ }
+
+ private void expire(K key) {
+ expirableData.remove(key);
+ expireOnCacheLoader(key);
+ }
+
+ private void expireOnCacheLoader(K key) {
+ if (cacheLoader == null && clm != null) cacheLoader =
clm.getCacheLoader();
+ if (cacheLoader != null) {
+ cacheLoader.remove(key);
+ }
+ }
+
@SuppressWarnings("unchecked")
private K maskNullKey(K o) {
return o == null ? (K) NULL : o;
@@ -48,54 +78,105 @@
@SuppressWarnings("unchecked")
public V get(K k) {
- CachedValue<V> cv = data.get(maskNullKey(k));
+ K maskedKey = maskNullKey(k);
+ CachedValue<V> cv = immortalData.get(maskedKey);
if (cv != null) {
cv.touch();
return cv.value;
} else {
- return null;
+ ExpirableCachedValue<V> ecv = expirableData.get(maskedKey);
+ if (ecv != null) {
+ if (ecv.expired()) {
+ expire(maskedKey);
+ } else {
+ ecv.touch();
+ return ecv.value;
+ }
+ }
}
+
+ return null;
}
- public void put(K k, V v) {
+ public void put(K k, V v, long lifespan) {
K maskedKey = maskNullKey(k);
- CachedValue<V> cv = data.get(maskedKey);
- if (cv == null) {
- cv = new CachedValue<V>(v);
- data.put(maskedKey, cv);
+ CachedValue<V> cv = immortalData.get(maskedKey);
+ ExpirableCachedValue<V> ecv;
+ if (cv != null) {
+ // do we need to move this to expirable?
+ if (lifespan < 0) {
+ // no.
+ cv.value = v;
+ cv.touch();
+ } else {
+ ecv = new ExpirableCachedValue<V>(v, lifespan);
+ immortalData.remove(maskedKey);
+ expirableData.put(maskedKey, ecv);
+ }
+ } else if ((ecv = expirableData.get(maskedKey)) != null) {
+ // do we need to move this to immortal?
+ if (lifespan < 0) {
+ // yes.
+ cv = new CachedValue<V>(v);
+ expirableData.remove(maskedKey);
+ immortalData.put(maskedKey, cv);
+ } else {
+ ecv.value = v;
+ ecv.touch();
+ }
} else {
- cv.touch();
- cv.value = v;
+ // does not exist anywhere!
+ if (lifespan < 0) {
+ cv = new CachedValue<V>(v);
+ immortalData.put(maskedKey, cv);
+ } else {
+ ecv = new ExpirableCachedValue<V>(v, lifespan);
+ expirableData.put(maskedKey, ecv);
+ }
}
}
public boolean containsKey(K k) {
- return data.containsKey(maskNullKey(k));
+ K maskedKey = maskNullKey(k);
+ if (!immortalData.containsKey(maskedKey)) {
+ ExpirableCachedValue<V> ecv = expirableData.get(maskedKey);
+ if (ecv == null) return false;
+ if (ecv.expired()) {
+ expire(maskedKey);
+ return false;
+ }
+ }
+ return true;
}
public long getModifiedTimestamp(K key) {
- CachedValue cv = data.get(maskNullKey(key));
+ K maskedKey = maskNullKey(key);
+ CachedValue cv = immortalData.get(maskedKey);
+ if (cv == null) cv = expirableData.get(maskedKey);
return cv == null ? -1 : cv.modified;
}
- public long getCreatedTimestamp(K key) {
- CachedValue cv = data.get(maskNullKey(key));
- return cv == null ? -1 : cv.created;
- }
-
@SuppressWarnings("unchecked")
public V remove(K k) {
- CachedValue<V> cv = data.remove(maskNullKey(k));
- return cv == null ? null : cv.value;
+ K maskedKey = maskNullKey(k);
+ CachedValue<V> cv = immortalData.remove(maskedKey);
+ if (cv == null) cv = expirableData.remove(maskedKey);
+
+ if (cv == null) {
+ return null;
+ } else {
+ return cv.value;
+ }
}
public int size() {
- return data.size();
+ return immortalData.size() + expirableData.size();
}
public void clear() {
- data.clear();
+ immortalData.clear();
+ expirableData.clear();
}
public Set<K> keySet() {
@@ -103,18 +184,34 @@
}
public String toString() {
- return data.toString();
+ return "Immortal Data: " + immortalData.toString() + "\n" +
"Expirable Data: " + expirableData.toString();
}
+ public Set<K> purgeExpiredEntries() {
+ Set<K> purged = new HashSet<K>();
+ for (Iterator<Map.Entry<K, ExpirableCachedValue<V>>> iter =
expirableData.entrySet().iterator(); iter.hasNext();) {
+ Map.Entry<K, ExpirableCachedValue<V>> entry = iter.next();
+ ExpirableCachedValue<?> cv = entry.getValue();
+ if (cv.expired()) {
+ iter.remove();
+ expireOnCacheLoader(entry.getKey());
+ purged.add(entry.getKey());
+ }
+ }
+ return purged;
+ }
+
private class KeySet extends AbstractSet<K> {
- Set<Object> realSet;
+ Set<K> immortalKeys;
+ Set<K> expirableKeys;
public KeySet() {
- this.realSet = data.keySet();
+ immortalKeys = immortalData.keySet();
+ expirableKeys = expirableData.keySet();
}
public Iterator<K> iterator() {
- return new KeyIterator(realSet.iterator());
+ return new KeyIterator(immortalKeys.iterator(), expirableKeys.iterator());
}
public void clear() {
@@ -122,7 +219,8 @@
}
public boolean contains(Object o) {
- return realSet.contains(maskNullKey((K) o));
+ K maskedKey = maskNullKey((K) o);
+ return immortalKeys.contains(maskedKey) || expirableKeys.contains(maskedKey);
}
public boolean remove(Object o) {
@@ -130,24 +228,37 @@
}
public int size() {
- return realSet.size();
+ return immortalKeys.size() + expirableKeys.size();
}
}
private class KeyIterator implements Iterator<K> {
- Iterator<Object> realIterator;
+ Iterator<Iterator<K>> metaIterator;
+ Iterator<K> immortalIterator;
+ Iterator<K> expirableIterator;
+ Iterator<K> currentIterator;
- private KeyIterator(Iterator<Object> realIterator) {
- this.realIterator = realIterator;
+ private KeyIterator(Iterator<K> immortalIterator, Iterator<K>
expirableIterator) {
+ List<Iterator<K>> iterators = new
ArrayList<Iterator<K>>(2);
+ iterators.add(immortalIterator);
+ iterators.add(expirableIterator);
+ metaIterator = iterators.iterator();
+
+ if (metaIterator.hasNext()) currentIterator = metaIterator.next();
}
public boolean hasNext() {
- return realIterator.hasNext();
+ boolean hasNext = currentIterator.hasNext();
+ while (!hasNext && metaIterator.hasNext()) {
+ currentIterator = metaIterator.next();
+ hasNext = currentIterator.hasNext();
+ }
+ return hasNext;
}
@SuppressWarnings("unchecked")
public K next() {
- return unmaskNullKey((K) realIterator.next());
+ return unmaskNullKey(currentIterator.next());
}
public void remove() {
@@ -157,22 +268,34 @@
private static class CachedValue<V> {
V value;
- long created;
long modified;
- public CachedValue(V value) {
+ private CachedValue(V value) {
this.value = value;
- created = modified = System.currentTimeMillis();
+ modified = System.currentTimeMillis();
}
- public CachedValue(V value, long created, long modified) {
- this.value = value;
- this.created = created;
- this.modified = modified;
+ void touch() {
+ modified = System.currentTimeMillis();
}
+ }
- public void touch() {
- modified = System.currentTimeMillis();
+ private static class ExpirableCachedValue<V> extends CachedValue<V> {
+ long created;
+ long expiryTime;
+
+ private ExpirableCachedValue(V value, long lifespan) {
+ super(value);
+ created = modified;
+ setLifespan(lifespan);
}
+
+ private boolean expired() {
+ return expiryTime >= 0 && System.currentTimeMillis() >
expiryTime;
+ }
+
+ private void setLifespan(long lifespan) {
+ expiryTime = lifespan < 0 ? -1 : lifespan + created;
+ }
}
}
\ No newline at end of file
Modified: core/branches/flat/src/main/java/org/horizon/eviction/EvictionAlgorithm.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/eviction/EvictionAlgorithm.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/eviction/EvictionAlgorithm.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -41,7 +41,7 @@
public interface EvictionAlgorithm extends Lifecycle {
/**
* Entry point for eviction algorithm. Invoking this will cause the algorithm to
process the queue of {@link
- * EvictionEvent}s passed in.
+ * org.horizon.eviction.events.EvictionEvent}s passed in.
*
* @param queue blocking queue of {@link org.horizon.eviction.events.EvictionEvent}s
to process.
* @throws EvictionException if there is a problem processing any of these events
Modified: core/branches/flat/src/main/java/org/horizon/eviction/EvictionManagerImpl.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/eviction/EvictionManagerImpl.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/eviction/EvictionManagerImpl.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -8,6 +8,7 @@
import org.horizon.container.DataContainer;
import org.horizon.eviction.events.EvictionEvent;
import org.horizon.eviction.events.InUseEvictionEvent;
+import org.horizon.eviction.events.PurgedDataEndEvent;
import org.horizon.factories.KnownComponentNames;
import org.horizon.factories.annotations.ComponentName;
import org.horizon.factories.annotations.Inject;
@@ -17,6 +18,7 @@
import org.horizon.logging.LogFactory;
import org.horizon.util.Util;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
@@ -87,6 +89,9 @@
class ScheduledTask implements Runnable {
public void run() {
+ registerEvictionEvent(new EvictionEvent(null,
EvictionEvent.Type.EXPIRED_DATA_PURGE_START));
+ Set purgedKeys = dataContainer.purgeExpiredEntries();
+ registerEvictionEvent(new PurgedDataEndEvent(purgedKeys));
processEvictionQueues();
}
}
Modified:
core/branches/flat/src/main/java/org/horizon/eviction/algorithms/BaseEvictionAlgorithm.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/eviction/algorithms/BaseEvictionAlgorithm.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/eviction/algorithms/BaseEvictionAlgorithm.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -32,12 +32,15 @@
import org.horizon.eviction.events.EvictionEvent;
import org.horizon.eviction.events.EvictionEvent.Type;
import org.horizon.eviction.events.InUseEvictionEvent;
+import org.horizon.eviction.events.PurgedDataEndEvent;
import org.horizon.logging.Log;
import org.horizon.logging.LogFactory;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -171,12 +174,14 @@
EvictionEvent event;
int count = 0;
long startTime = System.currentTimeMillis();
-
+ Set<Object> keysToRetainInQueue = null;
while ((event = nextEvent(queue)) != null) {
if (trace) count++;
switch (event.getEventType()) {
case ADD_ENTRY_EVENT:
- processAddedEntries(event.getKey());
+ Object key = event.getKey();
+ processAddedEntries(key);
+ recordEventKey(keysToRetainInQueue, key);
break;
case REMOVE_ENTRY_EVENT:
processRemovedEntries(event.getKey());
@@ -193,6 +198,14 @@
case UNMARK_IN_USE_EVENT:
processUnmarkInUseNodes(event.getKey());
break;
+ case EXPIRED_DATA_PURGE_START:
+ if (keysToRetainInQueue == null) keysToRetainInQueue = new
HashSet<Object>();
+ break;
+ case EXPIRED_DATA_PURGE_END:
+ Set<Object> keysPurged = ((PurgedDataEndEvent)
event).getKeysPurged();
+ if (keysToRetainInQueue != null)
keysPurged.removeAll(keysToRetainInQueue);
+ for (Object o : keysPurged) evictionQueue.remove(o);
+ break;
default:
throw new EvictionException("Illegal eviction event type " +
event.getEventType());
}
@@ -201,6 +214,10 @@
log.trace("processed {0} eviction events in {1} millis", count,
System.currentTimeMillis() - startTime);
}
+ private void recordEventKey(Set<Object> setToRecordIn, Object key) {
+ if (setToRecordIn != null) setToRecordIn.add(key);
+ }
+
protected void evictOrRecycle(Object key) {
log.trace("Attempting to evict {0}", key);
if (!action.evict(key)) {
Deleted: core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -1,98 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.horizon.eviction.events;
-
-/**
- * An eviction event records activity on nodes in the cache. These are recorded for
processing later.
- *
- * @author (various)
- * @since 1.0
- */
-public class EvictionEvent {
- Object key;
- Type type;
-
- public static enum Type {
- ADD_ENTRY_EVENT,
- REMOVE_ENTRY_EVENT,
- VISIT_ENTRY_EVENT,
- CLEAR_CACHE_EVENT,
- MARK_IN_USE_EVENT,
- UNMARK_IN_USE_EVENT
- }
-
- public EvictionEvent(Object key, Type type) {
- this.key = key;
- this.type = type;
- }
-
- public Object getKey() {
- return key;
- }
-
- public void setKey(Object key) {
- this.key = key;
- }
-
- public void setEventType(Type event) {
- type = event;
- }
-
- public Type getEventType() {
- return type;
- }
-
- @Override
- public String toString() {
- return "EvictedEventNode[key=" + key + " event=" + type +
"]";
- }
-
- /**
- * Copies this evicted event node to create a new one with the same values, except
with a new Fqn root.
- *
- * @param key new Fqn root to use
- * @return a new EvictedEventNode instance
- */
- public EvictionEvent copy(Object key) {
- return new EvictionEvent(key, type);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- EvictionEvent that = (EvictionEvent) o;
-
- if (key != null ? !key.equals(that.key) : that.key != null) return false;
- if (type != that.type) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = key != null ? key.hashCode() : 0;
- result = 31 * result + (type != null ? type.hashCode() : 0);
- return result;
- }
-}
Added: core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java
(rev 0)
+++
core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -0,0 +1,100 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.horizon.eviction.events;
+
+/**
+ * An eviction event records activity on nodes in the cache. These are recorded for
processing later.
+ *
+ * @author (various)
+ * @since 1.0
+ */
+public class EvictionEvent {
+ Object key;
+ Type type;
+
+ public static enum Type {
+ ADD_ENTRY_EVENT,
+ REMOVE_ENTRY_EVENT,
+ VISIT_ENTRY_EVENT,
+ CLEAR_CACHE_EVENT,
+ MARK_IN_USE_EVENT,
+ UNMARK_IN_USE_EVENT,
+ EXPIRED_DATA_PURGE_START, // internal marker to denote when purging of expired data
starts
+ EXPIRED_DATA_PURGE_END // internal marker to denote when purging of expired data
ends
+ }
+
+ public EvictionEvent(Object key, Type type) {
+ this.key = key;
+ this.type = type;
+ }
+
+ public Object getKey() {
+ return key;
+ }
+
+ public void setKey(Object key) {
+ this.key = key;
+ }
+
+ public void setEventType(Type event) {
+ type = event;
+ }
+
+ public Type getEventType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return "EvictedEventNode[key=" + key + " event=" + type +
"]";
+ }
+
+ /**
+ * Copies this evicted event node to create a new one with the same values, except
with a new Fqn root.
+ *
+ * @param key new Fqn root to use
+ * @return a new EvictedEventNode instance
+ */
+ public EvictionEvent copy(Object key) {
+ return new EvictionEvent(key, type);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ EvictionEvent that = (EvictionEvent) o;
+
+ if (key != null ? !key.equals(that.key) : that.key != null) return false;
+ if (type != that.type) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = key != null ? key.hashCode() : 0;
+ result = 31 * result + (type != null ? type.hashCode() : 0);
+ return result;
+ }
+}
Property changes on:
core/branches/flat/src/main/java/org/horizon/eviction/events/EvictionEvent.java
___________________________________________________________________
Name: svn:executable
+ *
Added:
core/branches/flat/src/main/java/org/horizon/eviction/events/PurgedDataEndEvent.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/eviction/events/PurgedDataEndEvent.java
(rev 0)
+++
core/branches/flat/src/main/java/org/horizon/eviction/events/PurgedDataEndEvent.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -0,0 +1,49 @@
+package org.horizon.eviction.events;
+
+import java.util.Set;
+
+/**
+ * To be put on an eviction event queue after expired data has been purged
+ *
+ * @author Manik Surtani
+ * @since 1.0
+ */
+public class PurgedDataEndEvent extends EvictionEvent {
+ Set<Object> keysPurged;
+
+ public PurgedDataEndEvent(Set<Object> keysPurged) {
+ super(null, EvictionEvent.Type.EXPIRED_DATA_PURGE_END);
+ this.keysPurged = keysPurged;
+ }
+
+ public Set<Object> getKeysPurged() {
+ return keysPurged;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ PurgedDataEndEvent that = (PurgedDataEndEvent) o;
+
+ if (keysPurged != null ? !keysPurged.equals(that.keysPurged) : that.keysPurged !=
null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (keysPurged != null ? keysPurged.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "PurgedDataEndEvent{" +
+ "keysPurged=" + keysPurged +
+ '}';
+ }
+}
Modified: core/branches/flat/src/main/java/org/horizon/factories/EntryFactory.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/factories/EntryFactory.java 2009-02-06
18:10:10 UTC (rev 7661)
+++ core/branches/flat/src/main/java/org/horizon/factories/EntryFactory.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -47,11 +47,11 @@
*/
boolean acquireLock(InvocationContext ctx, Object key) throws InterruptedException,
TimeoutException;
- MVCCEntry wrapEntryForWriting(InvocationContext ctx, Object key, boolean
createIfAbsent, boolean forceLockIfAbsent) throws InterruptedException;
+ MVCCEntry wrapEntryForWriting(InvocationContext ctx, Object key, boolean
createIfAbsent, boolean forceLockIfAbsent, long lifespan) throws InterruptedException;
MVCCEntry wrapEntryForReading(InvocationContext ctx, Object key, boolean putInContext,
boolean forceWriteLock) throws InterruptedException;
MVCCEntry wrapEntryForReading(InvocationContext ctx, Object key, boolean putInContext)
throws InterruptedException;
- MVCCEntry createWrappedEntry(Object key, Object value, boolean isForInsert);
+ MVCCEntry createWrappedEntry(Object key, Object value, boolean isForInsert, long
lifespan);
}
Modified: core/branches/flat/src/main/java/org/horizon/factories/EntryFactoryImpl.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/factories/EntryFactoryImpl.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/factories/EntryFactoryImpl.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -71,10 +71,10 @@
writeSkewCheck = configuration.isWriteSkewCheck();
}
- public MVCCEntry createWrappedEntry(Object key, Object value, boolean isForInsert) {
+ public MVCCEntry createWrappedEntry(Object key, Object value, boolean isForInsert,
long lifespan) {
if (value == null && !isForInsert) return useRepeatableRead ? NULL_MARKER :
null;
- MVCCEntry mvccEntry = useRepeatableRead ? new RepeatableReadEntry(key, value) : new
ReadCommittedEntry(key, value);
+ MVCCEntry mvccEntry = useRepeatableRead ? new RepeatableReadEntry(key, value,
lifespan) : new ReadCommittedEntry(key, value, lifespan);
return mvccEntry;
}
@@ -89,12 +89,12 @@
MVCCEntry mvccEntry;
if (forceWriteLock) {
if (trace) log.trace("Forcing lock on reading");
- return wrapEntryForWriting(ctx, key, false, false);
+ return wrapEntryForWriting(ctx, key, false, false, -1);
} else if ((mvccEntry = ctx.lookupEntry(key)) == null) {
if (trace) log.trace("Key " + key + " is not in context, fetching
from container.");
// simple implementation. Peek the node, wrap it, put wrapped node in the
context.
Object value = container.get(key);
- mvccEntry = createWrappedEntry(key, value, false);
+ mvccEntry = createWrappedEntry(key, value, false, -1);
if (mvccEntry != null && putInContext) ctx.putLookedUpEntry(key,
mvccEntry);
return mvccEntry;
} else {
@@ -103,7 +103,7 @@
}
}
- public MVCCEntry wrapEntryForWriting(InvocationContext ctx, Object key, boolean
createIfAbsent, boolean forceLockIfAbsent) throws InterruptedException {
+ public MVCCEntry wrapEntryForWriting(InvocationContext ctx, Object key, boolean
createIfAbsent, boolean forceLockIfAbsent, long lifespan) throws InterruptedException {
MVCCEntry mvccEntry = ctx.lookupEntry(key);
if (createIfAbsent && mvccEntry != null && mvccEntry.isNullEntry())
mvccEntry = null;
if (mvccEntry != null) // exists in context! Just acquire lock if needed, and
wrap.
@@ -128,17 +128,16 @@
// do we need a lock?
boolean needToCopy = false;
if (acquireLock(ctx, key)) needToCopy = true;
- mvccEntry = createWrappedEntry(key, value, false);
+ mvccEntry = createWrappedEntry(key, value, false, lifespan);
ctx.putLookedUpEntry(key, mvccEntry);
if (needToCopy) mvccEntry.copyForUpdate(container, writeSkewCheck);
- } else if (createIfAbsent) // else, do we need to create one?
- {
+ } else if (createIfAbsent) {
// this is the *only* point where new entries can be created!!
if (trace) log.trace("Creating new entry.");
// now to lock and create the node. Lock first to prevent concurrent
creation!
acquireLock(ctx, key);
notifier.notifyCacheEntryCreated(key, true, ctx);
- mvccEntry = createWrappedEntry(key, value, true);
+ mvccEntry = createWrappedEntry(key, value, true, lifespan);
mvccEntry.setCreated(true);
ctx.putLookedUpEntry(key, mvccEntry);
mvccEntry.copyForUpdate(container, writeSkewCheck);
Modified:
core/branches/flat/src/main/java/org/horizon/interceptors/CacheLoaderInterceptor.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/interceptors/CacheLoaderInterceptor.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/interceptors/CacheLoaderInterceptor.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -138,7 +138,7 @@
}
// Reuse the lock and create a new entry for loading
- MVCCEntry n = entryFactory.wrapEntryForWriting(ctx, key, true, false);
+ MVCCEntry n = entryFactory.wrapEntryForWriting(ctx, key, true, false, -1); // TODO:
handle expiry information from loaded data
n = loadEntry(ctx, key, n);
}
Modified:
core/branches/flat/src/main/java/org/horizon/interceptors/LockingInterceptor.java
===================================================================
---
core/branches/flat/src/main/java/org/horizon/interceptors/LockingInterceptor.java 2009-02-06
18:10:10 UTC (rev 7661)
+++
core/branches/flat/src/main/java/org/horizon/interceptors/LockingInterceptor.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -133,7 +133,7 @@
public Object visitClearCommand(InvocationContext ctx, ClearCommand command) throws
Throwable {
try {
// get a snapshot of all keys in the data container
- for (Object key : dataContainer.keySet()) entryFactory.wrapEntryForWriting(ctx,
key, false, false);
+ for (Object key : dataContainer.keySet()) entryFactory.wrapEntryForWriting(ctx,
key, false, false, -1);
return invokeNextInterceptor(ctx, command);
}
@@ -152,7 +152,7 @@
public Object visitInvalidateCommand(InvocationContext ctx, InvalidateCommand command)
throws Throwable {
try {
if (command.getKeys() != null) {
- for (Object key : command.getKeys()) entryFactory.wrapEntryForWriting(ctx,
key, false, true);
+ for (Object key : command.getKeys()) entryFactory.wrapEntryForWriting(ctx,
key, false, true, -1);
}
return invokeNextInterceptor(ctx, command);
}
@@ -164,7 +164,7 @@
@Override
public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand
command) throws Throwable {
try {
- entryFactory.wrapEntryForWriting(ctx, command.getKey(), true, false);
+ entryFactory.wrapEntryForWriting(ctx, command.getKey(), true, false,
command.getLifespanMillis());
Object o = invokeNextInterceptor(ctx, command);
return o;
}
@@ -177,7 +177,7 @@
public Object visitPutMapCommand(InvocationContext ctx, PutMapCommand command) throws
Throwable {
try {
for (Object key : command.getMap().keySet()) {
- entryFactory.wrapEntryForWriting(ctx, key, true, false);
+ entryFactory.wrapEntryForWriting(ctx, key, true, false,
command.getLifespanMillis());
}
return invokeNextInterceptor(ctx, command);
}
@@ -189,7 +189,7 @@
@Override
public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws
Throwable {
try {
- entryFactory.wrapEntryForWriting(ctx, command.getKey(), false, true);
+ entryFactory.wrapEntryForWriting(ctx, command.getKey(), false, true, -1);
return invokeNextInterceptor(ctx, command);
}
finally {
@@ -200,7 +200,7 @@
@Override
public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command)
throws Throwable {
try {
- entryFactory.wrapEntryForWriting(ctx, command.getKey(), false, true);
+ entryFactory.wrapEntryForWriting(ctx, command.getKey(), false, true,
command.getLifespanMillis());
return invokeNextInterceptor(ctx, command);
}
finally {
@@ -248,7 +248,7 @@
Object key = it.previous();
MVCCEntry entry = ctx.lookupEntry(key);
// could be null with read-committed
- if (entry != null) entry.commitUpdate(ctx, dataContainer);
+ if (entry != null) entry.commitUpdate(dataContainer);
// and then unlock
if (trace) log.trace("Releasing lock on [" + key + "] for
owner " + owner);
lockManager.unlock(key, owner);
Modified: core/branches/flat/src/main/java/org/horizon/util/AbstractMap.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/util/AbstractMap.java 2009-02-06 18:10:10
UTC (rev 7661)
+++ core/branches/flat/src/main/java/org/horizon/util/AbstractMap.java 2009-02-08 11:20:49
UTC (rev 7662)
@@ -43,52 +43,52 @@
return o1 == o2 || (o1 != null && o1.equals(o2));
}
-//
-// protected static class SimpleEntry<K, V> implements Map.Entry<K, V> {
-// private K key;
-// private V value;
-//
-// SimpleEntry(K key, V value) {
-// this.key = key;
-// this.value = value;
-// }
-//
-// SimpleEntry(Map.Entry<K, V> entry) {
-// this.key = entry.getKey();
-// this.value = entry.getValue();
-// }
-//
-// public K getKey() {
-// return key;
-// }
-//
-// public V getValue() {
-// return value;
-// }
-//
-// public V setValue(V value) {
-// V old = this.value;
-// this.value = value;
-// return old;
-// }
-//
-// public boolean equals(Object o) {
-// if (this == o)
-// return true;
-//
-// if (!(o instanceof Map.Entry))
-// return false;
-// Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
-// return eq(key, e.getKey()) && eq(value, e.getValue());
-// }
-//
-// public int hashCode() {
-// return hash(key) ^
-// (value == null ? 0 : hash(value));
-// }
-//
-// public String toString() {
-// return getKey() + "=" + getValue();
-// }
-// }
+
+ protected static class SimpleEntry<K, V> implements Map.Entry<K, V> {
+ private K key;
+ private V value;
+
+ SimpleEntry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ SimpleEntry(Map.Entry<K, V> entry) {
+ this.key = entry.getKey();
+ this.value = entry.getValue();
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V value) {
+ V old = this.value;
+ this.value = value;
+ return old;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
+ return eq(key, e.getKey()) && eq(value, e.getValue());
+ }
+
+ public int hashCode() {
+ return hash(key) ^
+ (value == null ? 0 : hash(value));
+ }
+
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+ }
}
Modified: core/branches/flat/src/main/java/org/horizon/util/FastCopyHashMap.java
===================================================================
--- core/branches/flat/src/main/java/org/horizon/util/FastCopyHashMap.java 2009-02-06
18:10:10 UTC (rev 7661)
+++ core/branches/flat/src/main/java/org/horizon/util/FastCopyHashMap.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -538,7 +538,7 @@
}
private class EntryIterator extends FasyCopyHashMapIterator<Map.Entry<K,
V>> {
- private class WriteThroughEntry extends java.util.AbstractMap.SimpleEntry<K,
V> {
+ private class WriteThroughEntry extends AbstractMap.SimpleEntry<K, V> {
WriteThroughEntry(K key, V value) {
super(key, value);
}
Deleted: core/branches/flat/src/test/java/org/horizon/BasicTest.java
===================================================================
--- core/branches/flat/src/test/java/org/horizon/BasicTest.java 2009-02-06 18:10:10 UTC
(rev 7661)
+++ core/branches/flat/src/test/java/org/horizon/BasicTest.java 2009-02-08 11:20:49 UTC
(rev 7662)
@@ -1,124 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * by the @authors tag. See the copyright.txt in the distribution for a
- * full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.horizon;
-
-import org.horizon.config.Configuration;
-import org.horizon.config.GlobalConfiguration;
-import org.horizon.logging.Log;
-import org.horizon.logging.LogFactory;
-import org.horizon.manager.CacheManager;
-import org.horizon.manager.DefaultCacheManager;
-import org.horizon.manager.NamedCacheNotFoundException;
-import org.horizon.util.TestingUtil;
-import org.testng.annotations.Test;
-
-@Test(groups = "functional")
-public class BasicTest {
- public static final Log log = LogFactory.getLog(BasicTest.class);
-
- public void basicTest() throws Exception {
- // create a cache manager
- Configuration c = new Configuration(); // LOCAL mode
- c.setFetchInMemoryState(false);
- DefaultCacheManager cm = new DefaultCacheManager(c);
- try {
- cm.start();
- Cache cache = cm.getCache("test");
- String key = "key", value = "value";
-
- assert cache.isEmpty();
- assert cache.size() == 0;
- assert !cache.containsKey(key);
-
- cache.put(key, value);
- assert cache.size() == 1;
- assert cache.containsKey(key);
- assert !cache.isEmpty();
-
- assert cache.remove(key).equals(value);
-
- assert cache.isEmpty();
- assert cache.size() == 0;
- assert !cache.containsKey(key);
- }
- finally {
- cm.stop();
- }
- }
-
- public void testBasicReplication() throws NamedCacheNotFoundException {
- Configuration configuration = new Configuration();
- configuration.setCacheMode(Configuration.CacheMode.REPL_SYNC);
-
- CacheManager firstManager = new
DefaultCacheManager(GlobalConfiguration.getClusteredDefault(), configuration);
- CacheManager secondManager = new
DefaultCacheManager(GlobalConfiguration.getClusteredDefault(), configuration);
-
- try {
- CacheSPI firstCache = (CacheSPI) firstManager.getCache();
- CacheSPI secondCache = (CacheSPI) secondManager.getCache();
- TestingUtil.blockUntilViewsReceived(60000, firstManager, secondManager);
-
- firstCache.put("key", "value");
-
- assert secondCache.get("key").equals("value");
- assert firstCache.get("key").equals("value");
- secondCache.put("key", "value2");
- assert firstCache.get("key").equals("value2");
- firstCache.remove("key");
- assert secondCache.get("key") == null;
- }
- finally {
- TestingUtil.killCacheManagers(firstManager, secondManager);
- }
- }
-
- public void concurrentMapMethodTest() {
- CacheManager cm = null;
- Cache<String, String> c = null;
- try {
- cm = new DefaultCacheManager();
- c = cm.getCache();
-
- assert c.putIfAbsent("A", "B") == null;
- assert c.putIfAbsent("A", "C").equals("B");
- assert c.get("A").equals("B");
-
- assert !c.remove("A", "C");
- assert c.containsKey("A");
- assert c.remove("A", "B");
- assert !c.containsKey("A");
-
- c.put("A", "B");
-
- assert !c.replace("A", "D", "C");
- assert c.get("A").equals("B");
- assert c.replace("A", "B", "C");
- assert c.get("A").equals("C");
-
- assert c.replace("A", "X").equals("C");
- assert c.replace("X", "A") == null;
- assert !c.containsKey("X");
- } finally {
- TestingUtil.killCacheManagers(cm);
- }
- }
-}
Deleted: core/branches/flat/src/test/java/org/horizon/SkipListTest.java
===================================================================
--- core/branches/flat/src/test/java/org/horizon/SkipListTest.java 2009-02-06 18:10:10 UTC
(rev 7661)
+++ core/branches/flat/src/test/java/org/horizon/SkipListTest.java 2009-02-08 11:20:49 UTC
(rev 7662)
@@ -1,64 +0,0 @@
-package org.horizon;
-
-import org.testng.annotations.Test;
-
-import java.util.TreeMap;
-
-/**
- * // TODO: Manik: Document this!
- *
- * @author Manik Surtani
- */
-@Test
-public class SkipListTest {
-
- public void test() {
- TreeMap m = new TreeMap();
- m.put(new Thing(50), "x");
- Thing t = new Thing(20);
- m.put(t, "x");
- m.put(new Thing(40), "x");
- m.put(new Thing(70), "x");
-
- System.out.println(m);
-
- t.number = 99;
- System.out.println(m);
- }
-
- public static class Thing implements Comparable {
- Integer number;
-
- public Thing(Integer number) {
- this.number = number;
- }
-
- public int compareTo(Object o) {
- return number.compareTo(((Thing) o).number);
- }
-
- @Override
- public String toString() {
- return "Thing{" +
- "number=" + number +
- '}';
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Thing thing = (Thing) o;
-
- if (number != null ? !number.equals(thing.number) : thing.number != null) return
false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return number != null ? number.hashCode() : 0;
- }
- }
-}
Modified: core/branches/flat/src/test/java/org/horizon/api/CacheAPITest.java
===================================================================
--- core/branches/flat/src/test/java/org/horizon/api/CacheAPITest.java 2009-02-06 18:10:10
UTC (rev 7661)
+++ core/branches/flat/src/test/java/org/horizon/api/CacheAPITest.java 2009-02-08 11:20:49
UTC (rev 7662)
@@ -1,5 +1,6 @@
package org.horizon.api;
+import org.horizon.Cache;
import org.horizon.CacheSPI;
import org.horizon.UnitTestCacheFactory;
import org.horizon.config.Configuration;
@@ -201,4 +202,71 @@
assert cache.get(key).equals(value);
assert 1 == cache.size();
}
+
+ public void testConcurrentMapMethods() {
+ CacheSPI<String, String> c = cacheTL.get();
+
+ assert c.putIfAbsent("A", "B") == null;
+ assert c.putIfAbsent("A", "C").equals("B");
+ assert c.get("A").equals("B");
+
+ assert !c.remove("A", "C");
+ assert c.containsKey("A");
+ assert c.remove("A", "B");
+ assert !c.containsKey("A");
+
+ c.put("A", "B");
+
+ assert !c.replace("A", "D", "C");
+ assert c.get("A").equals("B");
+ assert c.replace("A", "B", "C");
+ assert c.get("A").equals("C");
+
+ assert c.replace("A", "X").equals("C");
+ assert c.replace("X", "A") == null;
+ assert !c.containsKey("X");
+ }
+
+ public void testSizeAndContents() throws Exception {
+ Cache cache = cacheTL.get();
+ String key = "key", value = "value";
+
+ assert cache.isEmpty();
+ assert cache.size() == 0;
+ assert !cache.containsKey(key);
+
+ cache.put(key, value);
+ assert cache.size() == 1;
+ assert cache.containsKey(key);
+ assert !cache.isEmpty();
+
+ assert cache.remove(key).equals(value);
+
+ assert cache.isEmpty();
+ assert cache.size() == 0;
+ assert !cache.containsKey(key);
+
+ Map<String, String> m = new HashMap<String, String>();
+ m.put("1", "one");
+ m.put("2", "two");
+ m.put("3", "three");
+ cache.putAll(m);
+
+ assert cache.get("1").equals("one");
+ assert cache.get("2").equals("two");
+ assert cache.get("3").equals("three");
+ assert cache.size() == 3;
+
+ m = new HashMap<String, String>();
+ m.put("1", "newvalue");
+ m.put("4", "four");
+
+ cache.putAll(m);
+
+ assert cache.get("1").equals("newvalue");
+ assert cache.get("2").equals("two");
+ assert cache.get("3").equals("three");
+ assert cache.get("4").equals("four");
+ assert cache.size() == 4;
+ }
}
Added: core/branches/flat/src/test/java/org/horizon/container/DataContainerTest.java
===================================================================
--- core/branches/flat/src/test/java/org/horizon/container/DataContainerTest.java
(rev 0)
+++
core/branches/flat/src/test/java/org/horizon/container/DataContainerTest.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -0,0 +1,70 @@
+package org.horizon.container;
+
+import org.testng.annotations.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Test(groups = "unit")
+public class DataContainerTest {
+ public void testExpiredData() throws InterruptedException {
+ DataContainer dc = new UnsortedDataContainer();
+ dc.put("k", "v", -1);
+ Thread.sleep(1);
+
+ assert dc.getModifiedTimestamp("k") <= System.currentTimeMillis();
+
+ dc.get("k");
+ assert dc.purgeExpiredEntries().isEmpty();
+
+ dc.put("k", "v", 0);
+ Thread.sleep(1);
+ assert dc.size() == 1;
+ assert dc.getModifiedTimestamp("k") <= System.currentTimeMillis();
+
+ assert dc.get("k") == null;
+ assert dc.size() == 0;
+
+ dc.put("k", "v", 0);
+ Thread.sleep(1);
+ assert dc.size() == 1;
+ assert dc.getModifiedTimestamp("k") <= System.currentTimeMillis();
+
+ assert dc.purgeExpiredEntries().contains("k");
+ assert dc.size() == 0;
+ }
+
+ public void testExpirableToImmortal() {
+ UnsortedDataContainer dc = new UnsortedDataContainer();
+ dc.put("k", "v", 6000000);
+ assert dc.containsKey("k");
+ assert !dc.immortalData.containsKey("k");
+ assert dc.expirableData.containsKey("k");
+
+ dc.put("k", "v2", -1);
+ assert dc.containsKey("k");
+ assert dc.immortalData.containsKey("k");
+ assert !dc.expirableData.containsKey("k");
+
+ dc.put("k", "v3", 700000);
+ assert dc.containsKey("k");
+ assert !dc.immortalData.containsKey("k");
+ assert dc.expirableData.containsKey("k");
+ }
+
+ public void testKeySet() {
+ UnsortedDataContainer dc = new UnsortedDataContainer();
+ dc.put("k1", "v", 6000000);
+ dc.put("k2", "v", -1);
+
+ Set expected = new HashSet();
+ expected.add("k1");
+ expected.add("k2");
+
+ for (Object o : dc.keySet()) {
+ assert expected.remove(o);
+ }
+
+ assert expected.isEmpty();
+ }
+}
Added: core/branches/flat/src/test/java/org/horizon/expiry/ExpiryTest.java
===================================================================
--- core/branches/flat/src/test/java/org/horizon/expiry/ExpiryTest.java
(rev 0)
+++ core/branches/flat/src/test/java/org/horizon/expiry/ExpiryTest.java 2009-02-08
11:20:49 UTC (rev 7662)
@@ -0,0 +1,118 @@
+package org.horizon.expiry;
+
+import org.horizon.Cache;
+import org.horizon.manager.CacheManager;
+import org.horizon.manager.DefaultCacheManager;
+import org.horizon.util.TestingUtil;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Test(groups = "functional", sequential = true)
+public class ExpiryTest {
+
+ CacheManager cm;
+
+ @BeforeMethod
+ public void setUp() {
+ cm = new DefaultCacheManager();
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ TestingUtil.killCacheManagers(cm);
+ }
+
+ public void testExpiryInPut() throws InterruptedException {
+ Cache cache = cm.getCache();
+ long startTime = System.currentTimeMillis();
+ long lifespan = 1000;
+ cache.put("k", "v", lifespan, TimeUnit.MILLISECONDS);
+ while (System.currentTimeMillis() < startTime + lifespan + 100) {
+ if (System.currentTimeMillis() < startTime + lifespan) {
+ assert cache.get("k").equals("v");
+ } else {
+ assert cache.get("k") == null;
+ }
+ Thread.sleep(50);
+ }
+ }
+
+ public void testExpiryInPutAll() throws InterruptedException {
+ Cache cache = cm.getCache();
+ long startTime = System.currentTimeMillis();
+ long lifespan = 1000;
+ Map m = new HashMap();
+ m.put("k1", "v");
+ m.put("k2", "v");
+ cache.putAll(m, lifespan, TimeUnit.MILLISECONDS);
+ while (System.currentTimeMillis() < startTime + lifespan + 100) {
+ if (System.currentTimeMillis() < startTime + lifespan) {
+ assert cache.get("k1").equals("v");
+ assert cache.get("k2").equals("v");
+ } else {
+ assert cache.get("k1") == null;
+ assert cache.get("k2") == null;
+ }
+ Thread.sleep(50);
+ }
+ }
+
+ public void testExpiryInPutIfAbsent() throws InterruptedException {
+ Cache cache = cm.getCache();
+ long startTime = System.currentTimeMillis();
+ long lifespan = 1000;
+ assert cache.putIfAbsent("k", "v", lifespan,
TimeUnit.MILLISECONDS) == null;
+ while (System.currentTimeMillis() < startTime + lifespan + 100) {
+ if (System.currentTimeMillis() < startTime + lifespan) {
+ assert cache.get("k").equals("v");
+ } else {
+ assert cache.get("k") == null;
+ }
+ Thread.sleep(50);
+ }
+
+ cache.put("k", "v");
+ assert cache.putIfAbsent("k", "v", lifespan,
TimeUnit.MILLISECONDS) != null;
+ }
+
+ public void testExpiryInReplace() throws InterruptedException {
+ Cache cache = cm.getCache();
+ long lifespan = 1000;
+ assert cache.get("k") == null;
+ assert cache.replace("k", "v", lifespan, TimeUnit.MILLISECONDS)
== null;
+ assert cache.get("k") == null;
+ cache.put("k", "v-old");
+ assert cache.get("k").equals("v-old");
+ long startTime = System.currentTimeMillis();
+ assert cache.replace("k", "v", lifespan, TimeUnit.MILLISECONDS)
!= null;
+ assert cache.get("k").equals("v");
+ while (System.currentTimeMillis() < startTime + lifespan + 100) {
+ if (System.currentTimeMillis() < startTime + lifespan) {
+ assert cache.get("k").equals("v");
+ } else {
+ assert cache.get("k") == null;
+ }
+ Thread.sleep(50);
+ }
+
+ startTime = System.currentTimeMillis();
+ cache.put("k", "v");
+ assert cache.replace("k", "v", "v2", lifespan,
TimeUnit.MILLISECONDS);
+ while (System.currentTimeMillis() < startTime + lifespan + 100) {
+ if (System.currentTimeMillis() < startTime + lifespan) {
+ assert cache.get("k").equals("v2");
+ } else {
+ assert cache.get("k") == null;
+ }
+ Thread.sleep(50);
+ }
+ }
+
+ // TODO test that expiry lifespans survive eviction + subsequent cache loading
+ // TODO same for passivation
+}