Aidan Sadowski created COLLECTIONS-776: ------------------------------------------
Summary: Wrapping PassiveExpiringMap in a SynchonizedMap breaks expiration Key: COLLECTIONS-776 URL: https://issues.apache.org/jira/browse/COLLECTIONS-776 Project: Commons Collections Issue Type: Bug Components: Collection Affects Versions: 4.4 Environment: Java 8 Built on mac OS Catalina 10.15 using gradle 5.2.1 in Intellij Reporter: Aidan Sadowski The documentation for PassiveExpiringMap says "If you wish to use this map from multiple threads concurrently, you must use appropriate synchronization. The simplest approach is to wrap this map using [{{Collections.synchronizedMap(Map)}}|https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html?is-external=true#synchronizedMap-java.util.Map-]." However, wrapping a PassiveExpiringMap in a Collections.synchronizedMap seems to break the PassiveExpiringMap's expiration. Specifically, the operation passiveExirpringMap.keySet() no longer removes expired entries prior to returning (which it should, according to the PassiveExpiringMap doc). I wrote this simple program to prove it. It puts a key into two PassiveExpiringMaps with the same expiration policy of 15 seconds, one of which is wrapped in a SyncronizedMap. Then it loops through the maps, calling keySet() on them. The entry disappears from the unwrapped map after 15 seconds, but NOT the wrapped map! Code and log output below. {code:java} import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import org.apache.commons.collections4.map.PassiveExpiringMap; public class Main { // unwrapped static Map<String, String> unwrappedMap; // wrapped static Map<String, String> wrappedMap; public static void main(String[] args) throws InterruptedException { unwrappedMap = new PassiveExpiringMap<>(15, TimeUnit.SECONDS); wrappedMap = Collections.synchronizedMap(new PassiveExpiringMap<>(15, TimeUnit.SECONDS)); // Put something in the maps String key = UUID.randomUUID().toString(); logWithTimestamp("Putting key " + key); unwrappedMap.put(key, ""); wrappedMap.put(key, ""); // Check the map until the key has expired, sleeping 1 second between checks while (true) { Set<String> keysInUnwrappedMap = unwrappedMap.keySet(); Set<String> keysInWrappedMap = wrappedMap.keySet(); logWithTimestamp("Keys in unwrapped map: " + String.join(", ", keysInUnwrappedMap)); logWithTimestamp("Keys in wrapped map: " + String.join(", ", keysInWrappedMap)); Thread.sleep(1000); } } private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); private static void logWithTimestamp(String string) { Date resultdate = new Date(System.currentTimeMillis()); System.out.println("[" + sdf.format(resultdate) + "] " + string); } } {code} {code:java} [10:02:41] Putting key fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:41] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:41] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:42] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:42] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:43] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:43] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:44] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:44] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:45] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:45] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:46] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:46] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:47] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:47] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:48] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:48] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:49] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:49] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:50] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:50] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:51] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:51] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:52] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:52] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:53] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:53] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:54] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:54] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:55] Keys in unwrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:55] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:56] Keys in unwrapped map: [10:02:56] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:57] Keys in unwrapped map: [10:02:57] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:58] Keys in unwrapped map: [10:02:58] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 [10:02:59] Keys in unwrapped map: [10:02:59] Keys in wrapped map: fce9d058-8f13-4e6d-814e-90e03f575678 {code} -- This message was sent by Atlassian Jira (v8.3.4#803005)