package com.amazonaws.services.sqs; import static com.amazonaws.services.sqs.util.SQSQueueUtils.getLongMessageAttributeValue; import static com.amazonaws.services.sqs.util.SQSQueueUtils.longMessageAttributeValue; import static java.util.concurrent.Executors.callable; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import com.amazonaws.services.sqs.model.Message; import com.amazonaws.services.sqs.model.MessageSystemAttributeName; import com.amazonaws.services.sqs.model.SendMessageRequest; public class SQSScheduledExecutorService extends SQSExecutorService implements ScheduledExecutorService { // This needs to be lower than the deduplication window to ensure that cycling the messages // refreshes the deduplication timeout without any race conditions. private static final long MAX_SQS_DELAY_SECONDS = TimeUnit.SECONDS.convert(15, TimeUnit.MINUTES); public SQSScheduledExecutorService(AmazonSQSRequester sqsRequester, AmazonSQSResponder sqsResponder, String queueUrl, Consumer<Exception> exceptionHandler) { super(sqsRequester, sqsResponder, queueUrl, exceptionHandler); } /** * A scheduled version of an SQS task. Strongly modeled after * {@link ScheduledThreadPoolExecutor#ScheduledFutureTask}. */ private class ScheduledSQSFutureTask<T> extends SQSFutureTask<T> implements ScheduledFuture<T> { private static final String DELAY_NANOS_ATTRIBUTE_NAME = "DelayNanos"; private static final String PERIOD_NANOS_ATTRIBUTE_NAME = "PeriodNanos"; private long delay; /** * Period in seconds for repeating tasks. A positive * value indicates fixed-rate execution. A negative value * indicates fixed-delay execution. A value of 0 indicates a * non-repeating task. */ private final long period; /** * The time the task is enabled to execute in nanoTime units. * Only tracked for the benefit of getDelay() in the Delayed interface: * this implementation depends on the accuracy of the remaining delay instead. */ private long time; public ScheduledSQSFutureTask(Callable<T> callable, MessageContent messageContent, boolean withResponse, long delay, long period, TimeUnit unit) { super(callable, messageContent, withResponse); if (unit.ordinal() < TimeUnit.SECONDS.ordinal()) { throw new IllegalArgumentException("Delays at this precision not supported: " + unit); } this.delay = unit.toNanos(delay); this.period = unit.toNanos(period); messageContent.setMessageAttributesEntry(DELAY_NANOS_ATTRIBUTE_NAME, longMessageAttributeValue(this.delay)); messageContent.setMessageAttributesEntry(PERIOD_NANOS_ATTRIBUTE_NAME, longMessageAttributeValue(this.period)); this.time = getTime(this.delay); } public ScheduledSQSFutureTask(Message message) { super(message); this.delay = getLongMessageAttributeValue(messageContent.getMessageAttributes(), DELAY_NANOS_ATTRIBUTE_NAME).orElse(0L); this.period = getLongMessageAttributeValue(messageContent.getMessageAttributes(), PERIOD_NANOS_ATTRIBUTE_NAME).orElse(0L); decrementDelay(message); this.time = getTime(delay); } protected void decrementDelay(Message message) { long sendTimestamp = Long.parseLong(message.getAttributes().get(MessageSystemAttributeName.SentTimestamp.toString())); long receiveTimestamp = Long.parseLong(message.getAttributes().get(MessageSystemAttributeName.ApproximateFirstReceiveTimestamp.toString())); long dwellTime = receiveTimestamp - sendTimestamp; this.delay -= TimeUnit.NANOSECONDS.convert(dwellTime, TimeUnit.MILLISECONDS); messageContent.setMessageAttributesEntry(DELAY_NANOS_ATTRIBUTE_NAME, longMessageAttributeValue(delay)); } private long getTime(long delay) { return System.nanoTime() + delay; } @Override public long getDelay(TimeUnit unit) { return unit.convert(time - System.nanoTime(), TimeUnit.NANOSECONDS); } @Override public int compareTo(Delayed other) { if (other == this) { // compare zero if same object return 0; } if (other instanceof ScheduledSQSFutureTask) { ScheduledSQSFutureTask<?> x = (ScheduledSQSFutureTask<?>)other; long diff = time - x.time; if (diff < 0) { return -1; } else if (diff > 0) { return 1; } else { return 0; } } long d = getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS); return (d == 0) ? 0 : ((d < 0) ? -1 : 1); } @Override public SendMessageRequest toSendMessageRequest() { SendMessageRequest request = super.toSendMessageRequest(); int sqsDelaySeconds = Math.max(1, (int)Math.min(TimeUnit.NANOSECONDS.toSeconds(delay), MAX_SQS_DELAY_SECONDS)); request.setDelaySeconds(sqsDelaySeconds); return request; } public boolean isPeriodic() { return period != 0; } @Override public void run() { // Send back to the queue if the total delay hasn't elapsed yet. if (delay > 0) { send(); return; } this.time = System.nanoTime(); if (!isPeriodic()) { ScheduledSQSFutureTask.super.run(); } else if (ScheduledSQSFutureTask.super.runAndReset()) { setNextRunTime(); send(); } } /** * Sets the next time to run for a periodic task. */ private void setNextRunTime() { long p = period; if (p > 0) { // Delay from the start of run() time += p; delay = this.time - System.nanoTime(); } else { // Delay from now delay = -p; time = getTime(delay); } } } @Override protected SQSFutureTask<?> deserializeTask(Message message) { return new ScheduledSQSFutureTask<>(message); } public void delayedExecute(Runnable runnable, long delay, TimeUnit unit) { ScheduledSQSFutureTask<?> task = new ScheduledSQSFutureTask<>( callable(runnable, null), toMessageContent(runnable), false, delay, 0, unit); execute(task); } public void repeatWithFixedDelay(Runnable runnable, long initialDelay, long delay, TimeUnit unit) { ScheduledSQSFutureTask<?> task = new ScheduledSQSFutureTask<>( callable(runnable, null), toMessageContent(runnable), false, initialDelay, delay, unit); execute(task); } public void repeatAtFixedRate(Runnable runnable, long initialDelay, long delay, TimeUnit unit) { ScheduledSQSFutureTask<?> task = new ScheduledSQSFutureTask<>( callable(runnable, null), toMessageContent(runnable), false, initialDelay, -delay, unit); execute(task); } @Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { return schedule(callable(command, null), delay, unit); } @Override public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { ScheduledSQSFutureTask<V> task = new ScheduledSQSFutureTask<>( callable, toMessageContent(callable), true, delay, 0, unit); execute(task); return task; } @Override public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { ScheduledSQSFutureTask<?> task = new ScheduledSQSFutureTask<>( callable(command, null), toMessageContent(command), true, initialDelay, period, unit); execute(task); return task; } @Override public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { ScheduledSQSFutureTask<?> task = new ScheduledSQSFutureTask<>( callable(command, null), toMessageContent(command), true, initialDelay, -delay, unit); execute(task); return task; } }