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    }