All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.sqs.buffered.SendQueueBuffer Maven / Gradle / Ivy

Go to download

The Amazon Web Services SDK for Java provides Java APIs for building software on AWS' cost-effective, scalable, and reliable infrastructure products. The AWS Java SDK allows developers to code against APIs for all of Amazon's infrastructure web services (Amazon S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon AutoScaling, etc).

The newest version!
/*
 * Copyright 2012-2014 Amazon.com, Inc. or its affiliates. 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.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.amazonaws.services.sqs.buffered;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.model.BatchResultErrorEntry;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityBatchRequest;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityBatchRequestEntry;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityBatchResult;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityBatchResultEntry;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityRequest;
import com.amazonaws.services.sqs.model.DeleteMessageBatchRequest;
import com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry;
import com.amazonaws.services.sqs.model.DeleteMessageBatchResult;
import com.amazonaws.services.sqs.model.DeleteMessageBatchResultEntry;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageBatchRequest;
import com.amazonaws.services.sqs.model.SendMessageBatchRequestEntry;
import com.amazonaws.services.sqs.model.SendMessageBatchResult;
import com.amazonaws.services.sqs.model.SendMessageBatchResultEntry;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageResult;


/**
 * This class is responsible for buffering outgoing SQS requests, i.e. requests
 * to send a message, delete a message and change the visibility of the message. 
* * When a request arrives, the buffer adds the message to a message batch of an * appropriate type (creating such a batch if there currently isn't one * outstanding). When the outstanding batch becomes full, or when a configurable * timeout expires, the buffer makes a call to SQS to execute the current batch.
* * Internally, the batch objects maintain a list of futures corresponding to the * requests added to them. When a batch completes, it loads the results into the * futures and marks the futures as complete. * */ public class SendQueueBuffer { private static Log log = LogFactory.getLog(SendQueueBuffer.class); // Interface to support event notifications with a parameter. private interface Listener { void invoke(T o); }; /** Config settings for this buffer*/ private final QueueBufferConfig config; /** Url of our queue */ private final String qUrl; /** * The {@code AmazonSQS} client to use for this buffer's operations. */ private final AmazonSQS sqsClient; /** * The executor service for the batching tasks. */ private final Executor executor; /** * Object used to serialize sendMessage calls. */ private final Object sendMessageLock = new Object(); /** * Object used to serialize deleteMessage calls. */ private final Object deleteMessageLock = new Object(); /** * Object used to serialize changeMessageVisibility calls. */ private final Object changeMessageVisibilityLock = new Object(); /** * Current batching task for sendMessage. Using a size 1 array to allow * "passing by reference". Synchronized by {@code sendMessageLock}. */ private final SendMessageBatchTask[] openSendMessageBatchTask = new SendMessageBatchTask[1]; /** * Current batching task for deleteMessage. Using a size 1 array to allow * "passing by reference". Synchronized by {@code deleteMessageLock}. */ private final DeleteMessageBatchTask[] openDeleteMessageBatchTask = new DeleteMessageBatchTask[1]; /** * Current batching task for changeMessageVisibility. Using a size 1 array * to allow "passing by reference". Synchronized by * {@code changeMessageVisibilityLock}. */ private final ChangeMessageVisibilityBatchTask[] openChangeMessageVisibilityBatchTask = new ChangeMessageVisibilityBatchTask[1]; /** * Permits controlling the number of in flight SendMessage batches. */ private final Semaphore inflightSendMessageBatches; /** * Permits controlling the number of in flight DeleteMessage batches. */ private final Semaphore inflightDeleteMessageBatches; /** * Permits controlling the number of in flight ChangeMessageVisibility * batches. */ private final Semaphore inflightChangeMessageVisibilityBatches; SendQueueBuffer(AmazonSQS sqsClient, Executor executor, QueueBufferConfig paramConfig, String url) { this.sqsClient = sqsClient; this.executor = executor; this.config = paramConfig; qUrl = url; int maxBatch = config.getMaxInflightOutboundBatches(); //must allow at least one outbound batch. maxBatch = maxBatch > 0 ? maxBatch : 1; this.inflightSendMessageBatches = new Semaphore( maxBatch); this.inflightDeleteMessageBatches = new Semaphore( maxBatch); this.inflightChangeMessageVisibilityBatches = new Semaphore( maxBatch); } public QueueBufferConfig getConfig() { return config; } /** * @return never null */ public QueueBufferFuture< SendMessageRequest, SendMessageResult > sendMessage( SendMessageRequest request, QueueBufferCallback callback) { QueueBufferFuture result = submitOutboundRequest(sendMessageLock, openSendMessageBatchTask, request, inflightSendMessageBatches, callback); return result; } /** * @return never null */ public QueueBufferFuture deleteMessage(DeleteMessageRequest request, QueueBufferCallback callback) { return submitOutboundRequest(deleteMessageLock, openDeleteMessageBatchTask, request, inflightDeleteMessageBatches, callback); } /** * @return never null */ public QueueBufferFuture changeMessageVisibility(ChangeMessageVisibilityRequest request, QueueBufferCallback callback) { return submitOutboundRequest( changeMessageVisibilityLock, openChangeMessageVisibilityBatchTask, request, inflightChangeMessageVisibilityBatches, callback); } /** * @return new {@code OutboundBatchTask} of appropriate type, never null */ @SuppressWarnings("unchecked") private OutboundBatchTask newOutboundBatchTask( R request) { if (request instanceof SendMessageRequest) return (OutboundBatchTask) new SendMessageBatchTask(); else if (request instanceof DeleteMessageRequest) return (OutboundBatchTask) new DeleteMessageBatchTask(); else if (request instanceof ChangeMessageVisibilityRequest) return (OutboundBatchTask) new ChangeMessageVisibilityBatchTask(); else // this should never happen throw new IllegalArgumentException("Unsupported request type " + request.getClass().getName()); } /** * Flushes all outstanding outbound requests ({@code SendMessage}, * {@code DeleteMessage}, {@code ChangeMessageVisibility}) in this buffer. *

* The call returns successfully when all outstanding outbound requests * submitted before the call are completed (i.e. processed by SQS). */ public void flush() { try { synchronized (sendMessageLock) { inflightSendMessageBatches .acquire(config.getMaxInflightOutboundBatches()); inflightSendMessageBatches .release(config.getMaxInflightOutboundBatches()); } synchronized (deleteMessageLock) { inflightDeleteMessageBatches .acquire(config.getMaxInflightOutboundBatches()); inflightDeleteMessageBatches .release(config.getMaxInflightOutboundBatches()); } synchronized (changeMessageVisibilityLock) { inflightChangeMessageVisibilityBatches .acquire(config.getMaxInflightOutboundBatches()); inflightChangeMessageVisibilityBatches .release(config.getMaxInflightOutboundBatches()); } } catch( InterruptedException e) { Thread.currentThread().interrupt(); } } /** * Submits an outbound request for delivery to the queue associated with * this buffer. *

* * @param operationLock * the lock synchronizing calls for the call type ( * {@code sendMessage}, {@code deleteMessage}, * {@code changeMessageVisibility} ) * @param openOutboundBatchTask * the open batch task for this call type * @param request * the request to submit * @param inflightOperationBatches * the permits controlling the batches for this type of request * @return never null * @throws AmazonClientException * (see the various outbound calls for details) */ @SuppressWarnings("unchecked") , R extends AmazonWebServiceRequest, Result> QueueBufferFuture submitOutboundRequest( Object operationLock, OBT[] openOutboundBatchTask, R request, final Semaphore inflightOperationBatches, QueueBufferCallback callback) { /* * Callers add requests to a single batch task (openOutboundBatchTask) * until it is full or maxBatchOpenMs elapses. The total number of batch * task in flight is controlled by the inflightOperationBatch semaphore * capped at maxInflightOutboundBatches. */ QueueBufferFuture theFuture = null; try { synchronized (operationLock) { if (openOutboundBatchTask[0] == null || ((theFuture = openOutboundBatchTask[0].addRequest(request, callback))) == null) { OBT obt = (OBT) newOutboundBatchTask(request); inflightOperationBatches.acquire(); openOutboundBatchTask[0] = obt; // Register a listener for the event signaling that the // batch task has completed (successfully or not). openOutboundBatchTask[0].onCompleted = new Listener>() { public void invoke(OutboundBatchTask task) { inflightOperationBatches.release(); } }; if ( log.isTraceEnabled() ) { log.trace("Queue " + qUrl + " created new batch for " + request.getClass().toString() + " " + inflightOperationBatches.availablePermits() + " free slots remain"); } theFuture = openOutboundBatchTask[0].addRequest(request, callback); executor.execute(openOutboundBatchTask[0]); if ( null == theFuture ) { //this can happen only if the request itself is flawed, //so that it can't be added to any batch, even a brand //new one throw new AmazonClientException("Failed to schedule request "+ request + " for execution" ); } } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); AmazonClientException toThrow = new AmazonClientException("Interrupted while waiting for lock."); toThrow.initCause(e); throw toThrow; } return theFuture; } /** * Task to send a batch of outbound requests to SQS. *

* The batch task is constructed open and accepts requests until full, or * until {@code maxBatchOpenMs} elapses. At that point, the batch closes and * the collected requests are assembled into a single batch request to SQS. * Specialized for each type of outbound request. *

* Instances of this class (and subclasses) are thread-safe. * * @param * the type of the SQS request to batch * @param * the type of result he futures issued by this task will return */ private abstract class OutboundBatchTask implements Runnable { final List requests; final ArrayList> futures; AtomicBoolean open = new AtomicBoolean(true); volatile Listener> onCompleted = null; OutboundBatchTask() { requests = new ArrayList(config.getMaxBatchSize()); futures = new ArrayList>(config.getMaxBatchSize()); } /** * Adds a request to the batch if it is still open and has capacity. * * @return the future that can be used to get the results of the * execution, or null if the addition failed. */ synchronized QueueBufferFuture addRequest(R request, QueueBufferCallback callback) { if (!open.get()) return null; QueueBufferFuture theFuture = addIfAllowed(request, callback); // if the addition did not work, or this addition made us full, // we can close the request if ((null == theFuture) || isFull()) { open.set(false); } // the batch request is as full as it will ever be. no need to wait // for the timeout, we can run it now. if (!open.get()) notify(); return theFuture; } /** * Adds the request to the batch if capacity allows it. * * @param request * @return the future that will be signaled when the request is * completed and can be used to retrieve the result. Can be null * if the addition could not be done */ synchronized QueueBufferFuture addIfAllowed(R request, QueueBufferCallback callback) { if (isOkToAdd(request)) { requests.add(request); QueueBufferFuture theFuture = new QueueBufferFuture(callback); futures.add(theFuture); onRequestAdded(request); return theFuture; } else return null; } protected synchronized boolean isOkToAdd(R request) { return requests.size() < config.getMaxBatchSize(); } protected synchronized void onRequestAdded(R request) { // to be overridden by subclasses } /** * @return whether the buffer is filled to capacity */ synchronized boolean isFull() { return requests.size() >= config.getMaxBatchSize(); } /** * Processes the batch once closed. */ abstract void process(); @Override public synchronized void run() { try { long deadlineMs = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS ) + config.getMaxBatchOpenMs() +1; long t = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS ); while (open.get() && (t < deadlineMs ) ) { t = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS ); //zero means "wait forever", can't have that. long toWait = Math.max(1, deadlineMs - t); wait(toWait); } open.set(false); process(); } catch (InterruptedException e) { failAll( e ); } catch (AmazonClientException e) { failAll( e ); } catch (RuntimeException e) { failAll( e ); throw e; } catch (Error e) { failAll( new AmazonClientException("Error encountered", e) ); throw e; } finally { //make a copy of the listener since it can be modified from outside Listener> completionListener = onCompleted; if (completionListener != null) completionListener.invoke(this); } } private void failAll( Exception e) { for( QueueBufferFuture f : futures ) { f.setFailure(e); } } } private class SendMessageBatchTask extends OutboundBatchTask { int batchSizeBytes = 0; @Override protected synchronized boolean isOkToAdd(SendMessageRequest request) { return ( requests.size() < config.getMaxBatchSize() ) && ((request.getMessageBody().getBytes().length + batchSizeBytes) < config.getMaxBatchSizeBytes()); } @Override protected void onRequestAdded(SendMessageRequest request) { batchSizeBytes += request.getMessageBody().getBytes().length; } @Override synchronized boolean isFull() { return ( requests.size() >= config.getMaxBatchSize() ) || ( batchSizeBytes >= config.getMaxBatchSizeBytes()); } @Override void process() { if (requests.isEmpty()) return; SendMessageBatchRequest batchRequest = new SendMessageBatchRequest() .withQueueUrl(qUrl); ResultConverter.appendUserAgent(batchRequest, AmazonSQSBufferedAsyncClient.USER_AGENT); List entries = new ArrayList( requests.size()); for (int i = 0, n = requests.size(); i < n; i++) entries.add(new SendMessageBatchRequestEntry() .withId(Integer.toString(i)) .withMessageBody(requests.get(i).getMessageBody()) .withDelaySeconds(requests.get(i).getDelaySeconds())); batchRequest.setEntries(entries); SendMessageBatchResult batchResult = sqsClient .sendMessageBatch(batchRequest); for (SendMessageBatchResultEntry entry : batchResult .getSuccessful()) { int index = Integer.parseInt(entry.getId()); futures.get(index).setSuccess(ResultConverter.convert(entry)); } for (BatchResultErrorEntry errorEntry : batchResult.getFailed()) { int index = Integer.parseInt(errorEntry.getId()); if ( errorEntry.isSenderFault() ) { futures.get(index).setFailure( ResultConverter.convert(errorEntry)); } else { //retry. try { //this will retry internally up to 3 times. futures.get(index).setSuccess(sqsClient.sendMessage(requests.get(index))); } catch ( AmazonClientException ace ) { futures.get(index).setFailure(ace); } } } } } private class DeleteMessageBatchTask extends OutboundBatchTask { @Override void process() { if (requests.isEmpty()) return; DeleteMessageBatchRequest batchRequest = new DeleteMessageBatchRequest() .withQueueUrl(qUrl); ResultConverter.appendUserAgent(batchRequest, AmazonSQSBufferedAsyncClient.USER_AGENT); List entries = new ArrayList( requests.size()); for (int i = 0, n = requests.size(); i < n; i++) entries.add(new DeleteMessageBatchRequestEntry().withId( Integer.toString(i)).withReceiptHandle( requests.get(i).getReceiptHandle())); batchRequest.setEntries(entries); DeleteMessageBatchResult batchResult = sqsClient .deleteMessageBatch(batchRequest); for (DeleteMessageBatchResultEntry entry : batchResult .getSuccessful()) { int index = Integer.parseInt(entry.getId()); futures.get(index).setSuccess(null); } for (BatchResultErrorEntry errorEntry : batchResult.getFailed()) { int index = Integer.parseInt(errorEntry.getId()); if ( errorEntry.isSenderFault() ) { futures.get(index).setFailure( ResultConverter.convert(errorEntry)); } else { try { //retry. sqsClient.deleteMessage(requests.get(index)); futures.get(index).setSuccess(null); } catch ( AmazonClientException ace ) { futures.get(index).setFailure(ace); } } } } } private class ChangeMessageVisibilityBatchTask extends OutboundBatchTask { @Override void process() { if (requests.isEmpty()) return; ChangeMessageVisibilityBatchRequest batchRequest = new ChangeMessageVisibilityBatchRequest() .withQueueUrl(qUrl); ResultConverter.appendUserAgent(batchRequest, AmazonSQSBufferedAsyncClient.USER_AGENT); List entries = new ArrayList( requests.size()); for (int i = 0, n = requests.size(); i < n; i++) entries.add(new ChangeMessageVisibilityBatchRequestEntry() .withId(Integer.toString(i)) .withReceiptHandle(requests.get(i).getReceiptHandle()) .withVisibilityTimeout( requests.get(i).getVisibilityTimeout())); batchRequest.setEntries(entries); ChangeMessageVisibilityBatchResult batchResult = sqsClient .changeMessageVisibilityBatch(batchRequest); for (ChangeMessageVisibilityBatchResultEntry entry : batchResult .getSuccessful()) { int index = Integer.parseInt(entry.getId()); futures.get(index).setSuccess(null); } for (BatchResultErrorEntry errorEntry : batchResult.getFailed()) { int index = Integer.parseInt(errorEntry.getId()); if ( errorEntry.isSenderFault() ) { futures.get(index).setFailure( ResultConverter.convert(errorEntry)); } else { try { //retry. sqsClient.changeMessageVisibility(requests.get(index)); futures.get(index).setSuccess(null); } catch ( AmazonClientException ace ) { futures.get(index).setFailure(ace); } } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy