/** * Creates a new EntryTaskScheduler that will run all second operations in bulk. * Imagine a write-behind map where dirty entries will be stored in bulk. * Note that each key can only occur once; meaning you cannot delay the execution. * For example, once an entry is marked as dirty, it will run in write-delay-seconds, * even if the entry is updated again within write-delay-seconds. * Two things to remember: * 1. A key cannot be re-scheduled (postponing its execution). * 2. All entries scheduled for a given second will be executed once by your * SecondBulkExecutor implementation. * Once a key is executed, it can be re-scheduled for another execution. * <p/> * EntryTaskScheduler implementation is thread-safe. * * @param taskScheduler ScheduledExecutorService instance to execute the second * @param entryProcessor bulk processor * @return EntryTaskScheduler that will run all second operations in bulk */ public static <K, V> EntryTaskScheduler<K, V> newScheduler(TaskScheduler taskScheduler, ScheduledEntryProcessor<K, V> entryProcessor, ScheduleType scheduleType) { return new SecondsBasedEntryTaskScheduler<K, V>(taskScheduler, entryProcessor, scheduleType); }
private ScheduledEntry<K, V> cancelByCompositeKey(K key) { Set<CompositeKey> candidateKeys = getCompositeKeys(key); ScheduledEntry<K, V> result = null; for (CompositeKey compositeKey : candidateKeys) { Integer second = secondsOfKeys.remove(compositeKey); if (second == null) { continue; } Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries == null) { continue; } result = cancelAndCleanUpIfEmpty(second, entries, compositeKey); } return result; }
private boolean schedulePostponeEntry(long delayMillis, K key, V value) { int delaySeconds = ceilToSecond(delayMillis); Integer newSecond = findRelativeSecond(delayMillis); synchronized (mutex) { Integer existingSecond = secondsOfKeys.put(key, newSecond); if (existingSecond != null) { if (existingSecond.equals(newSecond)) { return false; } removeKeyFromSecond(key, existingSecond); } long id = uniqueIdGenerator.incrementAndGet(); ScheduledEntry<K, V> scheduledEntry = new ScheduledEntry<K, V>(key, value, delayMillis, delaySeconds, id); doSchedule(key, scheduledEntry, newSecond); } return true; }
private boolean scheduleEntry(long delayMillis, K key, V value) { int delaySeconds = ceilToSecond(delayMillis); Integer newSecond = findRelativeSecond(delayMillis); synchronized (mutex) { long id = uniqueIdGenerator.incrementAndGet(); Object compositeKey = new CompositeKey(key, id); secondsOfKeys.put(compositeKey, newSecond); ScheduledEntry<K, V> scheduledEntry = new ScheduledEntry<K, V>(key, value, delayMillis, delaySeconds, id); doSchedule(compositeKey, scheduledEntry, newSecond); } return true; }
@Override public ScheduledEntry<K, V> cancel(K key) { synchronized (mutex) { if (scheduleType.equals(ScheduleType.FOR_EACH)) { return cancelByCompositeKey(key); } Integer second = secondsOfKeys.remove(key); if (second == null) { return null; } Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries == null) { return null; } return cancelAndCleanUpIfEmpty(second, entries, key); } }
/** * Removes the entry from being scheduled to be evicted. * <p/> * Cleans up parent container (second -> entries map) if it doesn't hold anymore items this second. * <p/> * Cancels associated scheduler (second -> scheduler map ) if there are no more items to remove for this second. * <p/> * Returns associated scheduled entry. * * @param second second at which this entry was scheduled to be evicted * @param entries entries which were already scheduled to be evicted for this second * @param key entry key * @return associated scheduled entry */ private ScheduledEntry<K, V> cancelAndCleanUpIfEmpty(Integer second, Map<Object, ScheduledEntry<K, V>> entries, Object key) { ScheduledEntry<K, V> result = entries.remove(key); cleanUpScheduledFuturesIfEmpty(second, entries); return result; }
/** * Returns the duration in seconds between the time this class was loaded and now+{@code delayMillis} */ // package private for testing static int findRelativeSecond(long delayMillis) { long now = Clock.currentTimeMillis(); long d = (now + delayMillis - INITIAL_TIME_MILLIS); return ceilToSecond(d); }
private void removeKeyFromSecond(Object key, Integer existingSecond) { Map<Object, ScheduledEntry<K, V>> scheduledKeys = scheduledEntries.get(existingSecond); if (scheduledKeys != null) { cancelAndCleanUpIfEmpty(existingSecond, scheduledKeys, key); } }
@Override public ScheduledEntry<K, V> get(K key) { synchronized (mutex) { if (scheduleType.equals(ScheduleType.FOR_EACH)) { return getByCompositeKey(key); } Integer second = secondsOfKeys.get(key); if (second != null) { Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries != null) { return entries.get(key); } } return null; } }
/** * Returns one scheduled entry for the given {@code key} with no guaranteed ordering */ public ScheduledEntry<K, V> getByCompositeKey(K key) { Set<CompositeKey> candidateKeys = getCompositeKeys(key); ScheduledEntry<K, V> result = null; for (CompositeKey compositeKey : candidateKeys) { Integer second = secondsOfKeys.get(compositeKey); if (second != null) { Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries != null) { result = entries.get(compositeKey); } } } return result; }
private boolean scheduleEntry(long delayMillis, K key, V value) { int delaySeconds = ceilToSecond(delayMillis); Integer newSecond = findRelativeSecond(delayMillis); synchronized (mutex) { long id = uniqueIdGenerator.incrementAndGet(); Object compositeKey = new CompositeKey(key, id); secondsOfKeys.put(compositeKey, newSecond); ScheduledEntry<K, V> scheduledEntry = new ScheduledEntry<K, V>(key, value, delayMillis, delaySeconds, id); doSchedule(compositeKey, scheduledEntry, newSecond); } return true; }
@Override public ScheduledEntry<K, V> cancel(K key) { synchronized (mutex) { if (scheduleType.equals(ScheduleType.FOR_EACH)) { return cancelByCompositeKey(key); } Integer second = secondsOfKeys.remove(key); if (second == null) { return null; } Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries == null) { return null; } return cancelAndCleanUpIfEmpty(second, entries, key); } }
/** * Removes the entry from being scheduled to be evicted. * <p/> * Cleans up parent container (second -> entries map) if it doesn't hold anymore items this second. * <p/> * Cancels associated scheduler (second -> scheduler map ) if there are no more items to remove for this second. * <p/> * Returns associated scheduled entry. * * @param second second at which this entry was scheduled to be evicted * @param entries entries which were already scheduled to be evicted for this second * @param key entry key * @return associated scheduled entry */ private ScheduledEntry<K, V> cancelAndCleanUpIfEmpty(Integer second, Map<Object, ScheduledEntry<K, V>> entries, Object key) { ScheduledEntry<K, V> result = entries.remove(key); cleanUpScheduledFuturesIfEmpty(second, entries); return result; }
/** * Returns the duration in seconds between the time this class was loaded and now+{@code delayMillis} */ // package private for testing static int findRelativeSecond(long delayMillis) { long now = Clock.currentTimeMillis(); long d = (now + delayMillis - INITIAL_TIME_MILLIS); return ceilToSecond(d); }
private void removeKeyFromSecond(Object key, Integer existingSecond) { Map<Object, ScheduledEntry<K, V>> scheduledKeys = scheduledEntries.get(existingSecond); if (scheduledKeys != null) { cancelAndCleanUpIfEmpty(existingSecond, scheduledKeys, key); } }
@Override public ScheduledEntry<K, V> get(K key) { synchronized (mutex) { if (scheduleType.equals(ScheduleType.FOR_EACH)) { return getByCompositeKey(key); } Integer second = secondsOfKeys.get(key); if (second != null) { Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries != null) { return entries.get(key); } } return null; } }
/** * Returns one scheduled entry for the given {@code key} with no guaranteed ordering */ public ScheduledEntry<K, V> getByCompositeKey(K key) { Set<CompositeKey> candidateKeys = getCompositeKeys(key); ScheduledEntry<K, V> result = null; for (CompositeKey compositeKey : candidateKeys) { Integer second = secondsOfKeys.get(compositeKey); if (second != null) { Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries != null) { result = entries.get(compositeKey); } } } return result; }
private boolean schedulePostponeEntry(long delayMillis, K key, V value) { int delaySeconds = ceilToSecond(delayMillis); Integer newSecond = findRelativeSecond(delayMillis); synchronized (mutex) { Integer existingSecond = secondsOfKeys.put(key, newSecond); if (existingSecond != null) { if (existingSecond.equals(newSecond)) { return false; } removeKeyFromSecond(key, existingSecond); } long id = uniqueIdGenerator.incrementAndGet(); ScheduledEntry<K, V> scheduledEntry = new ScheduledEntry<K, V>(key, value, delayMillis, delaySeconds, id); doSchedule(key, scheduledEntry, newSecond); } return true; }
private ScheduledEntry<K, V> cancelByCompositeKey(K key) { Set<CompositeKey> candidateKeys = getCompositeKeys(key); ScheduledEntry<K, V> result = null; for (CompositeKey compositeKey : candidateKeys) { Integer second = secondsOfKeys.remove(compositeKey); if (second == null) { continue; } Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries == null) { continue; } result = cancelAndCleanUpIfEmpty(second, entries, compositeKey); } return result; }
@Override public int cancelIfExists(K key, V value) { synchronized (mutex) { ScheduledEntry<K, V> scheduledEntry = new ScheduledEntry<K, V>(key, value, 0, 0, 0); if (scheduleType.equals(ScheduleType.FOR_EACH)) { return cancelByCompositeKey(key, scheduledEntry); } Integer second = secondsOfKeys.remove(key); if (second == null) { return 0; } Map<Object, ScheduledEntry<K, V>> entries = scheduledEntries.get(second); if (entries == null) { return 0; } return cancelAndCleanUpIfEmpty(second, entries, key, scheduledEntry) ? 1 : 0; } }