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    }