#600 TimeoutMap now longer gets Long.MAX_VALUE as next expiry time if map is empty when removeExpiredEntries() is invoked.

This commit is contained in:
Harald Kuhr 2021-04-07 22:31:13 +02:00
parent 46ce99e10f
commit 913a03608c
3 changed files with 44 additions and 17 deletions

View File

@ -330,7 +330,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
} }
/** /**
* A simple Map.Entry implementaton. * A simple Map.Entry implementation.
*/ */
static class BasicEntry<K, V> implements Entry<K, V>, Serializable { static class BasicEntry<K, V> implements Entry<K, V>, Serializable {
K mKey; K mKey;

View File

@ -34,7 +34,7 @@ import java.io.Serializable;
import java.util.*; import java.util.*;
/** /**
* A {@code Map} implementation that removes (exipres) its elements after * A {@code Map} implementation that removes (expires) its elements after
* a given period. The map is by default backed by a {@link java.util.HashMap}, * a given period. The map is by default backed by a {@link java.util.HashMap},
* or can be instantiated with any given {@code Map} as backing. * or can be instantiated with any given {@code Map} as backing.
* <p> * <p>
@ -67,7 +67,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
protected long expiryTime = 60000L; // 1 minute protected long expiryTime = 60000L; // 1 minute
////////////////////// //////////////////////
private volatile long nextExpiryTime; private volatile long nextExpiryTime = Long.MAX_VALUE;
////////////////////// //////////////////////
/** /**
@ -178,7 +178,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
* @return {@code true} if this map contains no key-value mappings. * @return {@code true} if this map contains no key-value mappings.
*/ */
public boolean isEmpty() { public boolean isEmpty() {
return (size() <= 0); return size() <= 0;
} }
/** /**
@ -208,7 +208,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
* @see #containsKey(java.lang.Object) * @see #containsKey(java.lang.Object)
*/ */
public V get(Object pKey) { public V get(Object pKey) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.get(pKey); TimedEntry entry = (TimedEntry) entries.get(pKey);
if (entry == null) { if (entry == null) {
return null; return null;
@ -236,7 +236,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
* {@code null} values. * {@code null} values.
*/ */
public V put(K pKey, V pValue) { public V put(K pKey, V pValue) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.get(pKey); TimedEntry entry = (TimedEntry) entries.get(pKey);
V oldValue; V oldValue;
if (entry == null) { if (entry == null) {
@ -272,7 +272,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
* {@code null} values. * {@code null} values.
*/ */
public V remove(Object pKey) { public V remove(Object pKey) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.remove(pKey); TimedEntry entry = (TimedEntry) entries.remove(pKey);
return (entry != null) ? entry.getValue() : null; return (entry != null) ? entry.getValue() : null;
} }
@ -284,13 +284,12 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
init(); init();
} }
/*protected*/ TimedEntry<K, V> createEntry(K pKey, V pValue) { /*protected*/ TimedEntry createEntry(K pKey, V pValue) {
return new TimedEntry<K, V>(pKey, pValue); return new TimedEntry(pKey, pValue);
} }
/** /**
* Removes any expired mappings. * Removes any expired mappings.
*
*/ */
protected void removeExpiredEntries() { protected void removeExpiredEntries() {
// Remove any expired elements // Remove any expired elements
@ -312,7 +311,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
long next = Long.MAX_VALUE; long next = Long.MAX_VALUE;
nextExpiryTime = next; // Avoid multiple runs... nextExpiryTime = next; // Avoid multiple runs...
for (Iterator<Entry<K, V>> iterator = new EntryIterator(); iterator.hasNext();) { for (Iterator<Entry<K, V>> iterator = new EntryIterator(); iterator.hasNext();) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) iterator.next(); TimedEntry entry = (TimedEntry) iterator.next();
//// ////
long expires = entry.expires(); long expires = entry.expires();
if (expires < next) { if (expires < next) {
@ -376,7 +375,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
while (mNext == null && mIterator.hasNext()) { while (mNext == null && mIterator.hasNext()) {
Entry<K, Entry<K, V>> entry = mIterator.next(); Entry<K, Entry<K, V>> entry = mIterator.next();
TimedEntry<K, V> timed = (TimedEntry<K, V>) entry.getValue(); TimedEntry timed = (TimedEntry) entry.getValue();
if (timed.isExpiredBy(mNow)) { if (timed.isExpiredBy(mNow)) {
// Remove from map, and continue // Remove from map, and continue
@ -425,19 +424,28 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
/** /**
* Keeps track of timed objects * Keeps track of timed objects
*/ */
private class TimedEntry<K, V> extends BasicEntry<K, V> { private class TimedEntry extends BasicEntry<K, V> {
private long mTimestamp; private long mTimestamp;
TimedEntry(K pKey, V pValue) { TimedEntry(K pKey, V pValue) {
super(pKey, pValue); super(pKey, pValue);
mTimestamp = System.currentTimeMillis(); updateTimestamp();
} }
public V setValue(V pValue) { public V setValue(V pValue) {
mTimestamp = System.currentTimeMillis(); updateTimestamp();
return super.setValue(pValue); return super.setValue(pValue);
} }
private void updateTimestamp() {
mTimestamp = System.currentTimeMillis();
long expires = expires();
if (expires < nextExpiryTime) {
nextExpiryTime = expires;
}
}
final boolean isExpired() { final boolean isExpired() {
return isExpiredBy(System.currentTimeMillis()); return isExpiredBy(System.currentTimeMillis());
} }

View File

@ -557,7 +557,7 @@ public class TimeoutMapTest extends MapAbstractTest {
// NOTE: Only wait fist time, to avoid slooow tests // NOTE: Only wait fist time, to avoid slooow tests
synchronized (this) { synchronized (this) {
try { try {
wait(60l); wait(60L);
} }
catch (InterruptedException e) { catch (InterruptedException e) {
} }
@ -591,7 +591,7 @@ public class TimeoutMapTest extends MapAbstractTest {
try { try {
wait(60l); wait(60l);
} }
catch (InterruptedException e) { catch (InterruptedException ignore) {
} }
} }
} }
@ -651,5 +651,24 @@ public class TimeoutMapTest extends MapAbstractTest {
assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey)); assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey));
assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey)); assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey));
} }
@Test
public void testContainsKeyOnEmptyMap() {
// See #600
Map<String, String> timeoutMap = new TimeoutMap<>(30);
assertFalse(timeoutMap.containsKey("xyz"));
timeoutMap.put("xyz", "xyz");
assertTrue(timeoutMap.containsKey("xyz"));
try {
Thread.sleep(50); // Let the item expire
}
catch (InterruptedException ignore) {
}
assertFalse(timeoutMap.containsKey("xyz"));
assertNull(timeoutMap.get("xyz"));
}
} }