package com.amazonaws.services.sqs; import static com.amazonaws.services.sqs.executors.DeduplicatedRunnable.deduplicated; import static com.amazonaws.services.sqs.executors.SerializableFunction.serializable; import static com.amazonaws.services.sqs.util.SQSQueueUtils.SQS_LIST_QUEUES_LIMIT; import static com.amazonaws.services.sqs.util.SQSQueueUtils.forEachQueue; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Map; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.amazonaws.services.sqs.executors.SerializableReference; import com.amazonaws.services.sqs.model.Message; import com.amazonaws.services.sqs.model.QueueDoesNotExistException; import com.amazonaws.services.sqs.util.SQSQueueUtils; @SuppressWarnings("squid:S2055") // SonarLint exception for the custom serialization approach. class IdleQueueSweeper extends SQSScheduledExecutorService implements Serializable { private static final long serialVersionUID = 27151245504960185L; private static final Log LOG = LogFactory.getLog(AmazonSQSIdleQueueDeletingClient.class); private final SerializableReference<IdleQueueSweeper> thisReference; private final Consumer<Exception> exceptionHandler; public IdleQueueSweeper(AmazonSQSRequester sqsRequester, AmazonSQSResponder sqsResponder, String queueUrl, String queueNamePrefix, long period, TimeUnit unit, Consumer<Exception> exceptionHandler) { super(sqsRequester, sqsResponder, queueUrl, exceptionHandler); thisReference = new SerializableReference<>(queueUrl, this); this.exceptionHandler = exceptionHandler; // Jitter the startup times to avoid throttling on tagging as much as possible. long initialDelay = ThreadLocalRandom.current().nextLong(period); // TODO-RS: Invoke this repeatedly over time to ensure the task is reset // if it ever dies for any reason. repeatAtFixedRate(deduplicated(() -> checkQueuesForIdleness(queueNamePrefix)), initialDelay, period, unit); } protected void checkQueuesForIdleness(String prefix) { LOG.info("Checking all queues begining with prefix " + prefix + " for idleness"); try { forEachQueue(this, serializable(p -> sqs.listQueues(p).getQueueUrls()), prefix, SQS_LIST_QUEUES_LIMIT, (Serializable & Consumer<String>) this::checkQueueForIdleness); } catch (RejectedExecutionException e) { // Already shutting down, ignore } catch (Exception e) { // Make sure the recurring task never throws so it doesn't terminate. String message = "Encountered error when checking queues for idleness (prefix = " + prefix + ")"; exceptionHandler.accept(new RuntimeException(message, e)); } } protected void checkQueueForIdleness(String queueUrl) { try { if (isQueueIdle(queueUrl) && SQSQueueUtils.isQueueEmpty(sqs, queueUrl)) { LOG.info("Deleting idle queue: " + queueUrl); sqs.deleteQueue(queueUrl); } } catch (QueueDoesNotExistException e) { // Queue already deleted so nothing to do. } } private boolean isQueueIdle(String queueUrl) { Map<String, String> queueTags = sqs.listQueueTags(queueUrl).getTags(); Long lastHeartbeat = AmazonSQSIdleQueueDeletingClient.getLongTag(queueTags, AmazonSQSIdleQueueDeletingClient.LAST_HEARTBEAT_TIMESTAMP_TAG); Long idleQueueRetentionPeriod = AmazonSQSIdleQueueDeletingClient.getLongTag(queueTags, AmazonSQSIdleQueueDeletingClient.IDLE_QUEUE_RETENTION_PERIOD_TAG); long currentTimestamp = System.currentTimeMillis(); return idleQueueRetentionPeriod != null && (lastHeartbeat == null || (currentTimestamp - lastHeartbeat) > idleQueueRetentionPeriod * 1000); } @Override protected SQSFutureTask<?> deserializeTask(Message message) { return thisReference.withScope(() -> super.deserializeTask(message)); } protected Object writeReplace() throws ObjectStreamException { return thisReference.proxy(); } @Override public void shutdown() { super.shutdown(); thisReference.close(); } }