001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.component.kestrel;
018
019 import java.util.concurrent.BlockingQueue;
020 import java.util.concurrent.CountDownLatch;
021 import java.util.concurrent.Exchanger;
022 import java.util.concurrent.ExecutorService;
023 import java.util.concurrent.LinkedBlockingQueue;
024 import java.util.concurrent.atomic.AtomicInteger;
025
026 import net.spy.memcached.MemcachedClient;
027 import org.apache.camel.Exchange;
028 import org.apache.camel.Processor;
029 import org.apache.camel.ShutdownRunningTask;
030 import org.apache.camel.impl.DefaultConsumer;
031 import org.apache.camel.spi.ShutdownAware;
032 import org.slf4j.Logger;
033 import org.slf4j.LoggerFactory;
034
035 /**
036 * A Camel consumer that polls a kestrel queue.
037 */
038 public class KestrelConsumer extends DefaultConsumer implements ShutdownAware {
039 private final KestrelEndpoint endpoint;
040 private final MemcachedClient memcachedClient;
041 private final BlockingQueue<Exchanger> exchangerQueue = new LinkedBlockingQueue<Exchanger>();
042 private ExecutorService pollerExecutor;
043 private ExecutorService handlerExecutor;
044 private volatile boolean shutdownPending;
045 private CountDownLatch shutdownLatch;
046 private AtomicInteger pendingExchangeCount = new AtomicInteger(0);
047
048 public KestrelConsumer(final KestrelEndpoint endpoint, Processor processor, final MemcachedClient memcachedClient) {
049 super(endpoint, processor);
050 this.endpoint = endpoint;
051 this.memcachedClient = memcachedClient;
052 }
053
054 @Override
055 protected void doStart() throws Exception {
056 log.info("Starting consumer for " + endpoint.getEndpointUri());
057
058 int poolSize = endpoint.getConfiguration().getConcurrentConsumers();
059
060 shutdownPending = false;
061
062 if (poolSize > 1) {
063 // We'll set the shutdown latch to poolSize + 1, since we'll also
064 // wait for the poller thread when shutting down.
065 shutdownLatch = new CountDownLatch(poolSize + 1);
066
067 // Fire up the handler thread pool
068 handlerExecutor = endpoint.getCamelContext().getExecutorServiceStrategy().newFixedThreadPool(this, "Handlers-" + endpoint.getEndpointUri(), poolSize);
069 for (int k = 0; k < poolSize; ++k) {
070 handlerExecutor.execute(new Handler());
071 }
072 } else {
073 // Since we only have concurrentConsumers=1, we'll do the handling
074 // inside the poller thread, so there will only be one thread to
075 // wait for on this latch.
076 shutdownLatch = new CountDownLatch(1);
077 }
078
079 // Fire up the single poller thread
080 pollerExecutor = endpoint.getCamelContext().getExecutorServiceStrategy().newSingleThreadExecutor(this, "Poller-" + endpoint.getEndpointUri());
081 pollerExecutor.submit(new Poller(poolSize > 1));
082
083 super.doStart();
084 }
085
086 @Override
087 protected void doStop() throws Exception {
088 log.info("Stopping consumer for " + endpoint.getEndpointUri());
089
090 if (pollerExecutor != null) {
091 endpoint.getCamelContext().getExecutorServiceStrategy().shutdownNow(pollerExecutor);
092 }
093 if (handlerExecutor != null) {
094 endpoint.getCamelContext().getExecutorServiceStrategy().shutdownNow(handlerExecutor);
095 }
096
097 super.doStop();
098 }
099
100 public boolean deferShutdown(ShutdownRunningTask shutdownRunningTask) {
101 return false;
102 }
103
104 public int getPendingExchangesSize() {
105 return pendingExchangeCount.get();
106 }
107
108 public void prepareShutdown() {
109 // Signal to our threads that shutdown is happening
110 shutdownPending = true;
111
112 if (log.isDebugEnabled()) {
113 log.debug("Preparing to shutdown, waiting for " + shutdownLatch.getCount() + " threads to complete.");
114 }
115
116 // Wait for all threads to end
117 try {
118 shutdownLatch.await();
119 } catch (InterruptedException e) {
120 // ignore
121 }
122 }
123
124 /**
125 * This single thread is responsible for reading objects from kestrel and
126 * dispatching them to the handler threads. The catch is that we don't
127 * want to poll kestrel until we know we have a handler thread available
128 * and waiting to handle whatever comes up. So the way we deal with that
129 * is...each handler thread has an exchanger used to "receive" objects
130 * from the kestrel reader thread. When a handler thread is ready for
131 * work, it simply puts its exchanger in the queue. The kestrel reader
132 * thread takes an exchanger from the queue (which will block until one
133 * is there), and *then* it can poll kestrel. Once an object is received
134 * from kestrel, it gets exchanged with the handler thread, which can
135 * take the object and process it. Repeat...
136 */
137 @SuppressWarnings("unchecked")
138 private final class Poller implements Runnable {
139 private boolean concurrent;
140
141 private Poller(boolean concurrent) {
142 this.concurrent = concurrent;
143 }
144
145 public void run() {
146 if (log.isTraceEnabled()) {
147 log.trace("Kestrel poller is running");
148 }
149
150 // Construct the target key that we'll be requesting from kestrel.
151 // Include the /t=... wait time as applicable.
152 String target;
153 if (endpoint.getConfiguration().getWaitTimeMs() > 0) {
154 target = endpoint.getQueue() + "/t=" + endpoint.getConfiguration().getWaitTimeMs();
155 } else {
156 target = endpoint.getQueue();
157 }
158
159 Exchanger exchanger = null;
160 while (isRunAllowed() && !shutdownPending) {
161 if (concurrent) {
162 // Wait until an exchanger is available, indicating that a
163 // handler thread is ready to handle the next request.
164 // Don't read from kestrel until we know a handler is ready.
165 try {
166 exchanger = exchangerQueue.take();
167 } catch (InterruptedException e) {
168 if (log.isDebugEnabled()) {
169 log.debug("Interrupted, are we stopping? " + (isStopping() || isStopped()));
170 }
171 continue;
172 }
173
174 // We have the exchanger, so there's a handler thread ready
175 // to handle whatever we may read...so read the next object
176 // from the queue.
177 }
178
179 // Poll kestrel until we get an object back
180 Object value = null;
181 while (isRunAllowed() && !shutdownPending) {
182 if (log.isTraceEnabled()) {
183 log.trace("Polling " + target);
184 }
185 try {
186 value = memcachedClient.get(target);
187 if (value != null) {
188 break;
189 }
190 } catch (Exception e) {
191 if (isRunAllowed() && !shutdownPending) {
192 getExceptionHandler().handleException("Failed to get object from kestrel", e);
193 }
194 }
195
196 // We didn't get a value back from kestrel
197 if (isRunAllowed() && !shutdownPending) {
198 if (endpoint.getConfiguration().getWaitTimeMs() > 0) {
199 // Kestrel did the blocking for us
200 } else {
201 // We're doing non-blocking get, so in between we
202 // should at least sleep some short period of time
203 // so this loop doesn't go nuts so tightly.
204 try {
205 Thread.sleep(100);
206 } catch (InterruptedException ignored) {
207 }
208 }
209 }
210 }
211
212 if (log.isTraceEnabled()) {
213 log.trace("Got object from " + target);
214 }
215
216 if (concurrent) {
217 // Pass the object to the handler thread via the exchanger.
218 // The handler will take it from there.
219 try {
220 exchanger.exchange(value);
221 } catch (InterruptedException e) {
222 if (log.isDebugEnabled()) {
223 log.debug("Interrupted, are we stopping? " + (isStopping() || isStopped()));
224 }
225 continue;
226 }
227 } else {
228 // We're non-concurrent, so handle it right here
229 pendingExchangeCount.incrementAndGet();
230 try {
231 // Create the exchange and let camel process/route it
232 Exchange exchange = null;
233 try {
234 exchange = endpoint.createExchange();
235 exchange.getIn().setBody(value);
236 getProcessor().process(exchange);
237 } catch (Exception e) {
238 if (exchange != null) {
239 getExceptionHandler().handleException("Error processing exchange", exchange, e);
240 } else {
241 getExceptionHandler().handleException(e);
242 }
243 }
244 } finally {
245 // Decrement our pending exchange counter
246 pendingExchangeCount.decrementAndGet();
247 }
248 }
249 }
250 if (log.isTraceEnabled()) {
251 log.trace("Finished polling " + target);
252 }
253
254 // Decrement the shutdown countdown latch
255 shutdownLatch.countDown();
256 }
257 }
258
259 @SuppressWarnings("unchecked")
260 private final class Handler implements Runnable {
261 private Exchanger exchanger = new Exchanger();
262
263 public void run() {
264 if (log.isTraceEnabled()) {
265 log.trace(Thread.currentThread().getName() + " is starting");
266 }
267
268 while (isRunAllowed() && !shutdownPending) {
269 // First things first, add our exchanger to the queue,
270 // indicating that we're ready for a hand-off of work
271 try {
272 exchangerQueue.put(exchanger);
273 } catch (InterruptedException e) {
274 if (log.isDebugEnabled()) {
275 log.debug("Interrupted, are we stopping? " + (isStopping() || isStopped()));
276 }
277 continue;
278 }
279
280 // Optimistically increment our internal pending exchange
281 // counter, anticipating getting a value back from the exchanger
282 pendingExchangeCount.incrementAndGet();
283 try {
284 // Now wait for an object to come through the exchanger
285 Object value;
286 try {
287 value = exchanger.exchange(this);
288 } catch (InterruptedException e) {
289 if (log.isDebugEnabled()) {
290 log.debug("Interrupted, are we stopping? " + (isStopping() || isStopped()));
291 }
292 continue;
293 }
294
295 if (log.isTraceEnabled()) {
296 log.trace("Got a value from the exchanger");
297 }
298
299 // Create the exchange and let camel process/route it
300 Exchange exchange = null;
301 try {
302 exchange = endpoint.createExchange();
303 exchange.getIn().setBody(value);
304 getProcessor().process(exchange);
305 } catch (Exception e) {
306 if (exchange != null) {
307 getExceptionHandler().handleException("Error processing exchange", exchange, e);
308 } else {
309 getExceptionHandler().handleException(e);
310 }
311 }
312 } finally {
313 // Decrement our pending exchange counter
314 pendingExchangeCount.decrementAndGet();
315 }
316 }
317
318 // Decrement the shutdown countdown latch
319 shutdownLatch.countDown();
320
321 if (log.isTraceEnabled()) {
322 log.trace(Thread.currentThread().getName() + " is finished");
323 }
324 }
325 }
326 }