/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.apache.camel.component.kestrel;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import net.spy.memcached.MemcachedClient;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.ShutdownRunningTask;
import org.apache.camel.impl.DefaultConsumer;
import org.apache.camel.spi.ShutdownAware;
/**
* A Camel consumer that polls a kestrel queue.
*/
public class KestrelConsumer extends DefaultConsumer implements ShutdownAware {
private final KestrelEndpoint endpoint;
private final MemcachedClient memcachedClient;
private final BlockingQueue<Exchanger<?>> exchangerQueue = new LinkedBlockingQueue<Exchanger<?>>();
private ExecutorService pollerExecutor;
private ExecutorService handlerExecutor;
private volatile boolean shutdownPending;
private CountDownLatch shutdownLatch;
private AtomicInteger pendingExchangeCount = new AtomicInteger(0);
public KestrelConsumer(final KestrelEndpoint endpoint, Processor processor, final MemcachedClient memcachedClient) {
super(endpoint, processor);
this.endpoint = endpoint;
this.memcachedClient = memcachedClient;
}
@Override
protected void doStart() throws Exception {
log.info("Starting consumer for " + endpoint.getEndpointUri());
int poolSize = endpoint.getConfiguration().getConcurrentConsumers();
shutdownPending = false;
if (poolSize > 1) {
// We'll set the shutdown latch to poolSize + 1, since we'll also
// wait for the poller thread when shutting down.
shutdownLatch = new CountDownLatch(poolSize + 1);
// Fire up the handler thread pool
handlerExecutor = endpoint.getCamelContext().getExecutorServiceManager().newFixedThreadPool(this, "Handlers-" + endpoint.getEndpointUri(), poolSize);
for (int k = 0; k < poolSize; ++k) {
handlerExecutor.execute(new Handler());
}
} else {
// Since we only have concurrentConsumers=1, we'll do the handling
// inside the poller thread, so there will only be one thread to
// wait for on this latch.
shutdownLatch = new CountDownLatch(1);
}
// Fire up the single poller thread
pollerExecutor = endpoint.getCamelContext().getExecutorServiceManager().newSingleThreadExecutor(this, "Poller-" + endpoint.getEndpointUri());
pollerExecutor.submit(new Poller(poolSize > 1));
super.doStart();
}
@Override
protected void doStop() throws Exception {
log.info("Stopping consumer for " + endpoint.getEndpointUri());
if (pollerExecutor != null) {
endpoint.getCamelContext().getExecutorServiceManager().shutdown(pollerExecutor);
pollerExecutor = null;
}
if (handlerExecutor != null) {
endpoint.getCamelContext().getExecutorServiceManager().shutdown(handlerExecutor);
handlerExecutor = null;
}
super.doStop();
}
public boolean deferShutdown(ShutdownRunningTask shutdownRunningTask) {
return false;
}
public int getPendingExchangesSize() {
return pendingExchangeCount.get();
}
@Override
public void prepareShutdown(boolean suspendOnly, boolean forced) {
// Signal to our threads that shutdown is happening
shutdownPending = true;
if (log.isDebugEnabled()) {
log.debug("Preparing to shutdown, waiting for {} threads to complete.", shutdownLatch.getCount());
}
// Wait for all threads to end
try {
shutdownLatch.await();
} catch (InterruptedException e) {
// ignore
}
}
/**
* This single thread is responsible for reading objects from kestrel and
* dispatching them to the handler threads. The catch is that we don't
* want to poll kestrel until we know we have a handler thread available
* and waiting to handle whatever comes up. So the way we deal with that
* is...each handler thread has an exchanger used to "receive" objects
* from the kestrel reader thread. When a handler thread is ready for
* work, it simply puts its exchanger in the queue. The kestrel reader
* thread takes an exchanger from the queue (which will block until one
* is there), and *then* it can poll kestrel. Once an object is received
* from kestrel, it gets exchanged with the handler thread, which can
* take the object and process it. Repeat...
*/
@SuppressWarnings("unchecked")
private final class Poller implements Runnable {
private boolean concurrent;
private Poller(boolean concurrent) {
this.concurrent = concurrent;
}
public void run() {
log.trace("Kestrel poller is running");
// Construct the target key that we'll be requesting from kestrel.
// Include the /t=... wait time as applicable.
String target;
if (endpoint.getConfiguration().getWaitTimeMs() > 0) {
target = endpoint.getQueue() + "/t=" + endpoint.getConfiguration().getWaitTimeMs();
} else {
target = endpoint.getQueue();
}
@SuppressWarnings("rawtypes")
Exchanger exchanger = null;
while (isRunAllowed() && !shutdownPending) {
if (concurrent) {
// Wait until an exchanger is available, indicating that a
// handler thread is ready to handle the next request.
// Don't read from kestrel until we know a handler is ready.
try {
exchanger = exchangerQueue.take();
} catch (InterruptedException e) {
if (log.isDebugEnabled()) {
log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
}
continue;
}
// We have the exchanger, so there's a handler thread ready
// to handle whatever we may read...so read the next object
// from the queue.
}
// Poll kestrel until we get an object back
Object value = null;
while (isRunAllowed() && !shutdownPending) {
log.trace("Polling {}", target);
try {
value = memcachedClient.get(target);
if (value != null) {
break;
}
} catch (Exception e) {
if (isRunAllowed() && !shutdownPending) {
getExceptionHandler().handleException("Failed to get object from kestrel", e);
}
}
// We didn't get a value back from kestrel
if (isRunAllowed() && !shutdownPending) {
if (endpoint.getConfiguration().getWaitTimeMs() > 0) {
// Kestrel did the blocking for us
} else {
// We're doing non-blocking get, so in between we
// should at least sleep some short period of time
// so this loop doesn't go nuts so tightly.
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
}
}
}
log.trace("Got object from {}", target);
if (concurrent) {
// Pass the object to the handler thread via the exchanger.
// The handler will take it from there.
try {
exchanger.exchange(value);
} catch (InterruptedException e) {
if (log.isDebugEnabled()) {
log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
}
continue;
}
} else {
// We're non-concurrent, so handle it right here
pendingExchangeCount.incrementAndGet();
try {
// Create the exchange and let camel process/route it
Exchange exchange = null;
try {
exchange = endpoint.createExchange();
exchange.getIn().setBody(value);
getProcessor().process(exchange);
} catch (Exception e) {
if (exchange != null) {
getExceptionHandler().handleException("Error processing exchange", exchange, e);
} else {
getExceptionHandler().handleException(e);
}
}
} finally {
// Decrement our pending exchange counter
pendingExchangeCount.decrementAndGet();
}
}
}
log.trace("Finished polling {}", target);
// Decrement the shutdown countdown latch
shutdownLatch.countDown();
}
}
private final class Handler implements Runnable {
private Exchanger<Handler> exchanger = new Exchanger<Handler>();
public void run() {
if (log.isTraceEnabled()) {
log.trace("{} is starting", Thread.currentThread().getName());
}
while (isRunAllowed() && !shutdownPending) {
// First things first, add our exchanger to the queue,
// indicating that we're ready for a hand-off of work
try {
exchangerQueue.put(exchanger);
} catch (InterruptedException e) {
if (log.isDebugEnabled()) {
log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
}
continue;
}
// Optimistically increment our internal pending exchange
// counter, anticipating getting a value back from the exchanger
pendingExchangeCount.incrementAndGet();
try {
// Now wait for an object to come through the exchanger
Object value;
try {
value = exchanger.exchange(this);
} catch (InterruptedException e) {
if (log.isDebugEnabled()) {
log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
}
continue;
}
log.trace("Got a value from the exchanger");
// Create the exchange and let camel process/route it
Exchange exchange = null;
try {
exchange = endpoint.createExchange();
exchange.getIn().setBody(value);
getProcessor().process(exchange);
} catch (Exception e) {
if (exchange != null) {
getExceptionHandler().handleException("Error processing exchange", exchange, e);
} else {
getExceptionHandler().handleException(e);
}
}
} finally {
// Decrement our pending exchange counter
pendingExchangeCount.decrementAndGet();
}
}
// Decrement the shutdown countdown latch
shutdownLatch.countDown();
if (log.isTraceEnabled()) {
log.trace("{} is finished", Thread.currentThread().getName());
}
}
}
}