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 {} threads to complete.", shutdownLatch.getCount());
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 log.trace("Kestrel poller is running");
147
148 // Construct the target key that we'll be requesting from kestrel.
149 // Include the /t=... wait time as applicable.
150 String target;
151 if (endpoint.getConfiguration().getWaitTimeMs() > 0) {
152 target = endpoint.getQueue() + "/t=" + endpoint.getConfiguration().getWaitTimeMs();
153 } else {
154 target = endpoint.getQueue();
155 }
156
157 Exchanger exchanger = null;
158 while (isRunAllowed() && !shutdownPending) {
159 if (concurrent) {
160 // Wait until an exchanger is available, indicating that a
161 // handler thread is ready to handle the next request.
162 // Don't read from kestrel until we know a handler is ready.
163 try {
164 exchanger = exchangerQueue.take();
165 } catch (InterruptedException e) {
166 if (log.isDebugEnabled()) {
167 log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
168 }
169 continue;
170 }
171
172 // We have the exchanger, so there's a handler thread ready
173 // to handle whatever we may read...so read the next object
174 // from the queue.
175 }
176
177 // Poll kestrel until we get an object back
178 Object value = null;
179 while (isRunAllowed() && !shutdownPending) {
180 log.trace("Polling {}", target);
181 try {
182 value = memcachedClient.get(target);
183 if (value != null) {
184 break;
185 }
186 } catch (Exception e) {
187 if (isRunAllowed() && !shutdownPending) {
188 getExceptionHandler().handleException("Failed to get object from kestrel", e);
189 }
190 }
191
192 // We didn't get a value back from kestrel
193 if (isRunAllowed() && !shutdownPending) {
194 if (endpoint.getConfiguration().getWaitTimeMs() > 0) {
195 // Kestrel did the blocking for us
196 } else {
197 // We're doing non-blocking get, so in between we
198 // should at least sleep some short period of time
199 // so this loop doesn't go nuts so tightly.
200 try {
201 Thread.sleep(100);
202 } catch (InterruptedException ignored) {
203 }
204 }
205 }
206 }
207
208 log.trace("Got object from {}", target);
209
210 if (concurrent) {
211 // Pass the object to the handler thread via the exchanger.
212 // The handler will take it from there.
213 try {
214 exchanger.exchange(value);
215 } catch (InterruptedException e) {
216 if (log.isDebugEnabled()) {
217 log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
218 }
219 continue;
220 }
221 } else {
222 // We're non-concurrent, so handle it right here
223 pendingExchangeCount.incrementAndGet();
224 try {
225 // Create the exchange and let camel process/route it
226 Exchange exchange = null;
227 try {
228 exchange = endpoint.createExchange();
229 exchange.getIn().setBody(value);
230 getProcessor().process(exchange);
231 } catch (Exception e) {
232 if (exchange != null) {
233 getExceptionHandler().handleException("Error processing exchange", exchange, e);
234 } else {
235 getExceptionHandler().handleException(e);
236 }
237 }
238 } finally {
239 // Decrement our pending exchange counter
240 pendingExchangeCount.decrementAndGet();
241 }
242 }
243 }
244 log.trace("Finished polling {}", target);
245
246 // Decrement the shutdown countdown latch
247 shutdownLatch.countDown();
248 }
249 }
250
251 @SuppressWarnings("unchecked")
252 private final class Handler implements Runnable {
253 private Exchanger exchanger = new Exchanger();
254
255 public void run() {
256 if (log.isTraceEnabled()) {
257 log.trace("{} is starting", Thread.currentThread().getName());
258 }
259
260 while (isRunAllowed() && !shutdownPending) {
261 // First things first, add our exchanger to the queue,
262 // indicating that we're ready for a hand-off of work
263 try {
264 exchangerQueue.put(exchanger);
265 } catch (InterruptedException e) {
266 if (log.isDebugEnabled()) {
267 log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
268 }
269 continue;
270 }
271
272 // Optimistically increment our internal pending exchange
273 // counter, anticipating getting a value back from the exchanger
274 pendingExchangeCount.incrementAndGet();
275 try {
276 // Now wait for an object to come through the exchanger
277 Object value;
278 try {
279 value = exchanger.exchange(this);
280 } catch (InterruptedException e) {
281 if (log.isDebugEnabled()) {
282 log.debug("Interrupted, are we stopping? {}", isStopping() || isStopped());
283 }
284 continue;
285 }
286
287 log.trace("Got a value from the exchanger");
288
289 // Create the exchange and let camel process/route it
290 Exchange exchange = null;
291 try {
292 exchange = endpoint.createExchange();
293 exchange.getIn().setBody(value);
294 getProcessor().process(exchange);
295 } catch (Exception e) {
296 if (exchange != null) {
297 getExceptionHandler().handleException("Error processing exchange", exchange, e);
298 } else {
299 getExceptionHandler().handleException(e);
300 }
301 }
302 } finally {
303 // Decrement our pending exchange counter
304 pendingExchangeCount.decrementAndGet();
305 }
306 }
307
308 // Decrement the shutdown countdown latch
309 shutdownLatch.countDown();
310
311 if (log.isTraceEnabled()) {
312 log.trace("{} is finished", Thread.currentThread().getName());
313 }
314 }
315 }
316 }