Package com.amazonaws.services.sqs.buffered

Source Code of com.amazonaws.services.sqs.buffered.SendQueueBuffer$SendMessageBatchTask

/*
* Copyright 2012 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. <br>
*
* 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. <br>
*
* 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<T> {
        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<SendMessageRequest, SendMessageResult> callback)
      {
          QueueBufferFuture<SendMessageRequest, SendMessageResult>  result =
                  submitOutboundRequest(sendMessageLock, openSendMessageBatchTask, request, inflightSendMessageBatches, callback);
          return result;
      }
     
      /**
       * @return never null
       */
      public QueueBufferFuture<DeleteMessageRequest, Void> deleteMessage(DeleteMessageRequest request, QueueBufferCallback<DeleteMessageRequest, Void > callback) {
          return submitOutboundRequest(deleteMessageLock,
                  openDeleteMessageBatchTask,
                  request,
                  inflightDeleteMessageBatches,
                  callback);
      }
     
      /**
       * @return never null
       */
      public QueueBufferFuture<ChangeMessageVisibilityRequest, Void> changeMessageVisibility(ChangeMessageVisibilityRequest request, QueueBufferCallback<ChangeMessageVisibilityRequest, Void> callback) {
          return submitOutboundRequest(
                changeMessageVisibilityLock,
                openChangeMessageVisibilityBatchTask,
                request,
                inflightChangeMessageVisibilityBatches,
                callback);
      }
     
    /**
     * @return new {@code OutboundBatchTask} of appropriate type, never null
     */
    @SuppressWarnings("unchecked")
    private <R extends AmazonWebServiceRequest, Result> OutboundBatchTask<R, Result> newOutboundBatchTask(
            R request) {
        if (request instanceof SendMessageRequest)
            return (OutboundBatchTask<R, Result>) new SendMessageBatchTask();
        else if (request instanceof DeleteMessageRequest)
            return (OutboundBatchTask<R, Result>) new DeleteMessageBatchTask();
        else if (request instanceof ChangeMessageVisibilityRequest)
            return (OutboundBatchTask<R, Result>) 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.
     * <p>
     * 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.
     * <p>
     *
     * @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")
    <OBT extends OutboundBatchTask<R, Result>, R extends AmazonWebServiceRequest, Result> QueueBufferFuture<R, Result> submitOutboundRequest(
            Object operationLock,
            OBT[] openOutboundBatchTask,
            R request,
            final Semaphore inflightOperationBatches,
            QueueBufferCallback<R, Result> 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<R, Result> 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<OutboundBatchTask<R, Result>>() {
                        public void invoke(OutboundBatchTask<R, Result> 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.
     * <p>
     * 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.
     * <p>
     * Instances of this class (and subclasses) are thread-safe.
     *
     * @param <R>
     *            the type of the SQS request to batch
     * @param <Result>
     *            the type of result he futures issued by this task will return
     */
    private abstract class OutboundBatchTask<R extends AmazonWebServiceRequest, Result> implements Runnable {
        final List<R> requests;
        final ArrayList<QueueBufferFuture<R, Result>> futures;
        AtomicBoolean open = new AtomicBoolean(true);
        volatile Listener<OutboundBatchTask<R, Result>> onCompleted = null;

        OutboundBatchTask() {
            requests = new ArrayList<R>(config.getMaxBatchSize());
            futures = new ArrayList<QueueBufferFuture<R, Result>>(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<R, Result> addRequest(R request, QueueBufferCallback<R, Result> callback) {
            if (!open.get())
                return null;

            QueueBufferFuture<R, Result> 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<R, Result> addIfAllowed(R request, QueueBufferCallback<R,Result> callback) {
            if (isOkToAdd(request)) {
                requests.add(request);

                QueueBufferFuture<R, Result> theFuture = new QueueBufferFuture<R, Result>(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<OutboundBatchTask<R, Result>> completionListener = onCompleted;
                if (completionListener != null)
                    completionListener.invoke(this);
            }
        }
       
        private void failAll( Exception e) {
            for( QueueBufferFuture<R, Result> f : futures ) {
                f.setFailure(e);
            }
        }
    }

    private class SendMessageBatchTask extends
            OutboundBatchTask<SendMessageRequest, SendMessageResult> {
        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<SendMessageBatchRequestEntry> entries = new ArrayList<SendMessageBatchRequestEntry>(
                    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<DeleteMessageRequest, Void> {

        @Override
        void process() {
            if (requests.isEmpty())
                return;

            DeleteMessageBatchRequest batchRequest = new DeleteMessageBatchRequest()
                    .withQueueUrl(qUrl);
            ResultConverter.appendUserAgent(batchRequest, AmazonSQSBufferedAsyncClient.USER_AGENT);

            List<DeleteMessageBatchRequestEntry> entries = new ArrayList<DeleteMessageBatchRequestEntry>(
                    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<ChangeMessageVisibilityRequest, Void> {

        @Override
        void process() {
            if (requests.isEmpty())
                return;

            ChangeMessageVisibilityBatchRequest batchRequest = new ChangeMessageVisibilityBatchRequest()
                    .withQueueUrl(qUrl);
            ResultConverter.appendUserAgent(batchRequest, AmazonSQSBufferedAsyncClient.USER_AGENT);

            List<ChangeMessageVisibilityBatchRequestEntry> entries = new ArrayList<ChangeMessageVisibilityBatchRequestEntry>(
                    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);
                    }
                }
            }

        }
    }

}
TOP

Related Classes of com.amazonaws.services.sqs.buffered.SendQueueBuffer$SendMessageBatchTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.