Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hazelcast.util.scheduler.SecondsBasedEntryTaskScheduler Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.util.scheduler;
import com.hazelcast.util.Clock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Schedule execution of an entry for seconds later.
* This is similar to a scheduled executor service, but instead of scheduling
* a execution for a specific millisecond, this service will
* schedule it with second proximity. For example, if delayMillis is 600 ms,
* then the entry will be scheduled to execute in 1 second. If delayMillis is 2400,
* then the entry will be scheduled to execute in 3 seconds. Therefore, delayMillis is
* ceil-ed to the next second. It gives up exact time scheduling to gain
* the power of:
* a) bulk execution of all operations within the same second
* or
* b) being able to reschedule (postpone) execution.
*
* @param entry key type
* @param entry value type
*/
final class SecondsBasedEntryTaskScheduler implements EntryTaskScheduler {
public static final int INITIAL_CAPACITY = 10;
public static final double FACTOR = 1000d;
private static final long INITIAL_TIME_MILLIS = Clock.currentTimeMillis();
private static final Comparator SCHEDULED_ENTRIES_COMPARATOR = new Comparator() {
@Override
public int compare(ScheduledEntry o1, ScheduledEntry o2) {
if (o1.getScheduleId() > o2.getScheduleId()) {
return 1;
} else if (o1.getScheduleId() < o2.getScheduleId()) {
return -1;
}
return 0;
}
};
private final Map secondsOfKeys = new HashMap(1000);
private final Map>> scheduledEntries
= new HashMap>>(1000);
private final ScheduledExecutorService scheduledExecutorService;
private final ScheduledEntryProcessor entryProcessor;
private final ScheduleType scheduleType;
private final Map scheduledTaskMap = new HashMap(1000);
private final AtomicLong uniqueIdGenerator = new AtomicLong();
private final Object mutex = new Object();
SecondsBasedEntryTaskScheduler(ScheduledExecutorService scheduledExecutorService,
ScheduledEntryProcessor entryProcessor, ScheduleType scheduleType) {
this.scheduledExecutorService = scheduledExecutorService;
this.entryProcessor = entryProcessor;
this.scheduleType = scheduleType;
}
@Override
public boolean schedule(long delayMillis, K key, V value) {
if (scheduleType.equals(ScheduleType.POSTPONE)) {
return schedulePostponeEntry(delayMillis, key, value);
} else if (scheduleType.equals(ScheduleType.FOR_EACH)) {
return scheduleEntry(delayMillis, key, value);
} else {
throw new RuntimeException("Undefined schedule type.");
}
}
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 scheduledEntry = new ScheduledEntry(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 scheduledEntry = new ScheduledEntry(key, value, delayMillis, delaySeconds, id);
doSchedule(compositeKey, scheduledEntry, newSecond);
}
return true;
}
private void doSchedule(Object mapKey, ScheduledEntry entry, Integer second) {
Map> entries = scheduledEntries.get(second);
boolean shouldSchedule = false;
if (entries == null) {
entries = new HashMap>(INITIAL_CAPACITY);
scheduledEntries.put(second, entries);
// we created the second
// so we will schedule its execution
shouldSchedule = true;
}
entries.put(mapKey, entry);
if (shouldSchedule) {
schedule(second, entry.getActualDelaySeconds());
}
}
@Override
public ScheduledEntry 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> entries = scheduledEntries.get(second);
if (entries == null) {
return null;
}
return cancelAndCleanUpIfEmpty(second, entries, key);
}
}
@Override
public int cancelIfExists(K key, V value) {
synchronized (mutex) {
ScheduledEntry scheduledEntry = new ScheduledEntry(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> entries = scheduledEntries.get(second);
if (entries == null) {
return 0;
}
return cancelAndCleanUpIfEmpty(second, entries, key, scheduledEntry) ? 1 : 0;
}
}
@Override
public ScheduledEntry get(K key) {
synchronized (mutex) {
if (scheduleType.equals(ScheduleType.FOR_EACH)) {
return getByCompositeKey(key);
}
Integer second = secondsOfKeys.get(key);
if (second != null) {
Map> entries = scheduledEntries.get(second);
if (entries != null) {
return entries.get(key);
}
}
return null;
}
}
private ScheduledEntry cancelByCompositeKey(K key) {
Set candidateKeys = getCompositeKeys(key);
ScheduledEntry result = null;
for (CompositeKey compositeKey : candidateKeys) {
Integer second = secondsOfKeys.remove(compositeKey);
if (second == null) {
continue;
}
Map> entries = scheduledEntries.get(second);
if (entries == null) {
continue;
}
result = cancelAndCleanUpIfEmpty(second, entries, compositeKey);
}
return result;
}
private int cancelByCompositeKey(K key, ScheduledEntry entryToRemove) {
int cancelled = 0;
for (CompositeKey compositeKey : getCompositeKeys(key)) {
Integer second = secondsOfKeys.remove(compositeKey);
if (second == null) {
continue;
}
Map> entries = scheduledEntries.get(second);
if (entries == null) {
continue;
}
if (cancelAndCleanUpIfEmpty(second, entries, compositeKey, entryToRemove)) {
cancelled++;
}
}
return cancelled;
}
private Set getCompositeKeys(K key) {
Set candidateKeys = new HashSet();
for (Object keyObj : secondsOfKeys.keySet()) {
CompositeKey compositeKey = (CompositeKey) keyObj;
if (compositeKey.getKey().equals(key)) {
candidateKeys.add(compositeKey);
}
}
return candidateKeys;
}
public ScheduledEntry getByCompositeKey(K key) {
Set candidateKeys = getCompositeKeys(key);
ScheduledEntry result = null;
for (CompositeKey compositeKey : candidateKeys) {
Integer second = secondsOfKeys.get(compositeKey);
if (second != null) {
Map> entries = scheduledEntries.get(second);
if (entries != null) {
result = entries.get(compositeKey);
}
}
}
return result;
}
private void removeKeyFromSecond(Object key, Integer existingSecond) {
Map> scheduledKeys = scheduledEntries.get(existingSecond);
if (scheduledKeys != null) {
cancelAndCleanUpIfEmpty(existingSecond, scheduledKeys, key);
}
}
/**
* Removes the entry from being scheduled to be evicted.
*
* Cleans up parent container (second -> entries map) if it doesn't hold anymore items this second.
*
* Cancels associated scheduler (second -> scheduler map ) if there are no more items to remove for this second.
*
* 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 cancelAndCleanUpIfEmpty(Integer second, Map> entries, Object key) {
ScheduledEntry result = entries.remove(key);
cleanUpScheduledFuturesIfEmpty(second, entries);
return result;
}
/**
* Removes the entry if it exists from being scheduled to be evicted.
*
* Cleans up parent container (second -> entries map) if it doesn't hold anymore items this second.
*
* Cancels associated scheduler (second -> scheduler map ) if there are no more items to remove for this second.
*
* 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
* @param entryToRemove entry value that is expected to exist in the map
* @return true if entryToRemove exists in the map and removed
*/
private boolean cancelAndCleanUpIfEmpty(Integer second, Map> entries, Object key,
ScheduledEntry entryToRemove) {
ScheduledEntry entry = entries.get(key);
if (entry == null || !entry.equals(entryToRemove)) {
return false;
}
entries.remove(key);
cleanUpScheduledFuturesIfEmpty(second, entries);
return true;
}
/**
* Cancels the scheduled future and removes the entries map for the given second If no entries are left
*
* Cleans up parent container (second -> entries map) if it doesn't hold anymore items this second.
*
* Cancels associated scheduler (second -> scheduler map ) if there are no more items to remove for this second.
*
* @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
*/
private void cleanUpScheduledFuturesIfEmpty(Integer second, Map> entries) {
if (entries.isEmpty()) {
scheduledEntries.remove(second);
ScheduledFuture removedFeature = scheduledTaskMap.remove(second);
if (removedFeature != null) {
removedFeature.cancel(false);
}
}
}
private void schedule(Integer second, int delaySeconds) {
EntryProcessorExecutor command = new EntryProcessorExecutor(second);
ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(command, delaySeconds, TimeUnit.SECONDS);
scheduledTaskMap.put(second, scheduledFuture);
}
/**
* used only for testing
*
* @return
*/
public int size() {
synchronized (mutex) {
return secondsOfKeys.size();
}
}
public void cancelAll() {
synchronized (mutex) {
secondsOfKeys.clear();
scheduledEntries.clear();
for (ScheduledFuture task : scheduledTaskMap.values()) {
task.cancel(false);
}
scheduledTaskMap.clear();
}
}
@Override
public String toString() {
return "EntryTaskScheduler{"
+ "secondsOfKeys="
+ secondsOfKeys.size()
+ ", scheduledEntries ["
+ scheduledEntries.size()
+ "] ="
+ scheduledEntries.keySet()
+ '}';
}
private static List> sortForEntryProcessing(List> coll) {
if (coll == null || coll.isEmpty()) {
return Collections.emptyList();
}
Collections.sort(coll, SCHEDULED_ENTRIES_COMPARATOR);
return coll;
}
private static int findRelativeSecond(long delayMillis) {
long now = Clock.currentTimeMillis();
long d = (now + delayMillis - INITIAL_TIME_MILLIS);
return ceilToSecond(d);
}
private static int ceilToSecond(long delayMillis) {
return (int) Math.ceil(delayMillis / FACTOR);
}
private final class EntryProcessorExecutor implements Runnable {
private final Integer second;
private EntryProcessorExecutor(Integer second) {
this.second = second;
}
@Override
public void run() {
List> values;
synchronized (mutex) {
scheduledTaskMap.remove(second);
Map> entries = scheduledEntries.remove(second);
if (entries == null || entries.isEmpty()) {
return;
}
values = new ArrayList>(entries.size());
for (Map.Entry> entry : entries.entrySet()) {
Integer removed = secondsOfKeys.remove(entry.getKey());
if (removed != null) {
values.add(entry.getValue());
}
}
}
//sort entries asc by schedule times and send to processor.
entryProcessor.process(SecondsBasedEntryTaskScheduler.this, sortForEntryProcessing(values));
}
}
}