/** * Copyright 2010 TransPac Software, Inc. * * Licensed 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 com.bixolabs.simpledb; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * A wrapper for ThreadPoolExecutor that implements a specific behavior we need in Bixo. * When execute() is called, it succeeds unless all of the threads are busy and the * specified timeout is exceeded (no threads finish up in that amount of time). * */ public class ThreadedExecutor { /** * Always wait for some time when offer() is called. This gives any * active threads that much time to complete, before a RejectedExectionException * is thrown. * * @param <E> element stored in queue */ @SuppressWarnings("serial") private class MyBlockingQueue<E> extends SynchronousQueue<E> { public MyBlockingQueue() { super(true); } @Override public boolean offer(E element) { try { return offer(element, _requestTimeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } } private long _requestTimeout; private ThreadPoolExecutor _pool; public ThreadedExecutor(int maxThreads, long requestTimeout) { _requestTimeout = requestTimeout; // With the "always offer with a timeout" queue, the maximumPoolSize should always // be set to the same as the corePoolSize, as otherwise things get very inefficient // since each execute() call will will delay by <timeout> even if we could add more // threads. And since these two values are the same, the keepAliveTime value has // no meaning (especially since we no longer incorrectly set allowCoreThreadTimeOut to true, // as if that's true then the timeout value still does apply). BlockingQueue<Runnable> queue = new MyBlockingQueue<Runnable>(); _pool = new ThreadPoolExecutor(maxThreads, maxThreads, 1, TimeUnit.SECONDS, queue); } /** * Execute <command> using the thread pool. * * @param command * @throws RejectedExecutionException */ public void execute(Runnable command) throws RejectedExecutionException { _pool.execute(command); } /** * Return number of active threads * * @return count of active threads */ public int getActiveCount() { return _pool.getActiveCount(); } /** * Terminate the thread pool. * * @return true if we did a normal termination, false if we had to do a hard shutdown * @throws InterruptedException */ public boolean terminate(long terminationTimeout) throws InterruptedException { // First just wait for threads to terminate naturally. _pool.shutdown(); if (_pool.awaitTermination(terminationTimeout, TimeUnit.MILLISECONDS)) { return true; } // We need to do a hard shutdown List<Runnable> remainingTasks = _pool.shutdownNow(); if (remainingTasks.size() != 0) { // Houston, we have a problem. Since ThreadedExecutor isn't multi-threaded, we should // never hit the one edge case where this _might_ be true (execute was called, waiting // for a thread to terminate, and then this terminate was called). throw new RuntimeException("There should never be any tasks in the queue"); } return false; } }