com.amazonaws.services.sqs.buffered.QueueBuffer Maven / Gradle / Ivy
/*
* Copyright 2012-2013 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.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import com.amazonaws.AmazonClientException;
import com.amazonaws.handlers.AsyncHandler;
import com.amazonaws.services.sqs.AmazonSQSAsync;
import com.amazonaws.services.sqs.model.ChangeMessageVisibilityRequest;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageResult;
/**
* A buffer to operate on an SQS queue. The buffer batches outbound ( {@code
* SendMessage}, {@code DeleteMessage}, {@code ChangeMessageVisibility})
* requests to the queue and pre-fetches messages to receive. In practice,
* the buffer does almost no work itself, and delegates it to SendQueueBufer
* and ReceiveQueueBuffer classes.
*
* Any errors encountered are passed through to the callers, either as the
* appropriate Result objects or as exceptions.
*
* When the buffer is not used, all internal processing associated with the
* buffer stops when any outstanding request to SQS completes. In that idle
* state, the buffer uses neither connections nor threads.
*
* Instances of {@code QueueBuffer} are thread-safe.
*/
class QueueBuffer {
private final SendQueueBuffer sendBuffer;
private final ReceiveQueueBuffer receiveBuffer;
private final AmazonSQSAsync realSqs;
QueueBufferConfig config;
/** This executor that will be shared among all queue buffers. Since a single JVM can
* access hundreds of queues, it won't do to have hundreds of executors spinning up
* hundreds of threads for each queue.
*
* The DaemonThreadFactory creates daemon threads, which means they won't block the JVM
* from exiting if only they are still around.
* */
static ExecutorService executor = Executors.newCachedThreadPool(new DaemonThreadFactory());;
QueueBuffer( QueueBufferConfig paramConfig, String url, AmazonSQSAsync sqs) {
realSqs = sqs;
config = paramConfig;
sendBuffer = new SendQueueBuffer(sqs, executor, paramConfig, url);
receiveBuffer = new ReceiveQueueBuffer(sqs, executor, paramConfig, url);
}
/**
* asynchronously enqueues a message to SQS.
*
* @return a Future object that will be notified when the operation is
* completed; never null
*/
public Future< SendMessageResult> sendMessage(SendMessageRequest request, AsyncHandler handler) {
QueueBufferCallback callback = null;
if ( handler != null ) {
callback = new QueueBufferCallback (handler, request);
}
QueueBufferFuture future = sendBuffer.sendMessage(request, callback);
future.setBuffer(this);
return future;
}
/**
* Sends a message to SQS and returns the SQS reply.
*
* @return never null
*/
public SendMessageResult sendMessageSync(SendMessageRequest request) {
Future future = sendMessage( request, null );
return waitForFuture(future);
}
/**
* Asynchronously deletes a message from SQS.
*
* @return a Future object that will be notified when the operation is
* completed; never null
*/
public Future deleteMessage(DeleteMessageRequest request, AsyncHandler handler) {
QueueBufferCallback callback = null;
if ( handler != null ) {
callback = new QueueBufferCallback (handler, request);
}
QueueBufferFuture future = sendBuffer.deleteMessage(request, callback);
future.setBuffer(this);
return future;
}
/**
* Deletes a message from SQS. Does not return until a confirmation from SQS
* has been received
*
* @return never null
*/
public void deleteMessageSync(DeleteMessageRequest request) {
Future future = deleteMessage(request, null);
waitForFuture(future);
}
/**
* asynchronously adjust a message's visibility timeout to SQS.
*
* @return a Future object that will be notified when the operation is
* completed; never null
*/
public Future changeMessageVisibility(ChangeMessageVisibilityRequest request, AsyncHandler handler) {
QueueBufferCallback callback = null;
if ( handler != null ) {
callback = new QueueBufferCallback (handler, request);
}
QueueBufferFuture future = sendBuffer.changeMessageVisibility(request, callback);
future.setBuffer(this);
return future;
}
/**
* Changes visibility of a message in SQS. Does not return until a
* confirmation from SQS has been received.
*/
public void changeMessageVisibilitySync(ChangeMessageVisibilityRequest request) {
Future future = sendBuffer.changeMessageVisibility(request, null);
waitForFuture(future);
}
/**
* Submits a request to receive some messages from SQS.
*
* @return a Future object that will be notified when the operation is
* completed; never null;
*/
public Future receiveMessage(ReceiveMessageRequest rq, AsyncHandler handler) {
//only handle simple requests, because these are the settings we pre-fetch with
boolean noAttributes = (rq.getAttributeNames() == null) || rq.getAttributeNames().isEmpty();
boolean bufferngEnabled = (config.getMaxInflightReceiveBatches() > 0) && (config.getMaxDoneReceiveBatches() > 0);
if ( noAttributes && bufferngEnabled && ( rq.getVisibilityTimeout() == null ) ) {
QueueBufferCallback callback = null;
if ( handler != null ) {
callback = new QueueBufferCallback (handler, rq);
}
QueueBufferFuture future = receiveBuffer.receiveMessageAsync(rq, callback);
future.setBuffer(this);
return future;
} else {
return realSqs.receiveMessageAsync(rq);
}
}
/**
* Retrieves messages from an SQS queue.
*
* @return never null
*/
public ReceiveMessageResult receiveMessageSync( ReceiveMessageRequest rq ) {
Future future = receiveMessage(rq, null);
return waitForFuture(future);
}
/**
* Shuts down the queue buffer. Once this method has been called, the
* queue buffer is not operational and all subsequent calls to it may fail
* */
public void shutdown() {
//send buffer does not require shutdown, only
//shut down receive buffer
receiveBuffer.shutdown();
}
/**
* this method carefully waits for futures. If waiting throws, it converts
* the exceptions to the exceptions that SQS clients expect. This is what we
* use to turn asynchronous calls into synchronous ones
*/
private ResultType waitForFuture( Future future ) {
ResultType toReturn = null;
try {
toReturn = future.get();
} catch ( InterruptedException ie ) {
Thread.currentThread().interrupt();
AmazonClientException ce = new AmazonClientException("Thread interrupted while waiting for execution result");
ce.initCause(ie);
throw ce;
} catch (ExecutionException ee ) {
//if the cause of the execution exception is an SQS exception, extract it
//and throw the extracted exception to the clients
//otherwise, wrap ee in an SQS exception and throw that.
Throwable cause = ee.getCause();
if (cause instanceof AmazonClientException) {
throw ( AmazonClientException ) cause;
}
AmazonClientException ce = new AmazonClientException("Caught an exception while waiting for request to complete...");
ce.initCause(ee);
throw ce;
}
return toReturn;
}
/**
* We need daemon threads in our executor so that we don't keep the process
* running if our executor threads are the only ones left in the process.
* */
private static class DaemonThreadFactory implements ThreadFactory {
static AtomicInteger threadCount = new AtomicInteger( 0 );
public Thread newThread( Runnable r) {
int threadNumber = threadCount.addAndGet(1);
Thread thread = new Thread( r );
thread.setDaemon(true);
thread.setName("SQSQueueBufferWorkerThread-" + threadNumber );
return thread;
}
}
}