Home | History | Annotate | Download | only in concurrent
      1 /*
      2  * Copyright (C) 2006 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.util.concurrent;
     18 
     19 import static com.google.common.base.Preconditions.checkArgument;
     20 import static com.google.common.base.Preconditions.checkNotNull;
     21 import com.google.common.base.Throwables;
     22 import com.google.common.collect.Sets;
     23 
     24 import java.lang.reflect.InvocationHandler;
     25 import java.lang.reflect.InvocationTargetException;
     26 import java.lang.reflect.Method;
     27 import java.lang.reflect.Proxy;
     28 import java.util.Set;
     29 import java.util.concurrent.Callable;
     30 import java.util.concurrent.ExecutionException;
     31 import java.util.concurrent.ExecutorService;
     32 import java.util.concurrent.Executors;
     33 import java.util.concurrent.Future;
     34 import java.util.concurrent.TimeUnit;
     35 import java.util.concurrent.TimeoutException;
     36 
     37 /**
     38  * A TimeLimiter that runs method calls in the background using an
     39  * {@link ExecutorService}.  If the time limit expires for a given method call,
     40  * the thread running the call will be interrupted.
     41  *
     42  * @author Kevin Bourrillion
     43  * @since 2009.09.15 <b>tentative</b>
     44  */
     45 public class SimpleTimeLimiter implements TimeLimiter {
     46 
     47   private final ExecutorService executor;
     48 
     49   /**
     50    * Constructs a TimeLimiter instance using the given executor service to
     51    * execute proxied method calls.
     52    * <p>
     53    * <b>Warning:</b> using a bounded executor
     54    * may be counterproductive!  If the thread pool fills up, any time callers
     55    * spend waiting for a thread may count toward their time limit, and in
     56    * this case the call may even time out before the target method is ever
     57    * invoked.
     58    *
     59    * @param executor the ExecutorService that will execute the method calls on
     60    *     the target objects; for example, a {@link
     61    *     java.util.concurrent.Executors#newCachedThreadPool()}.
     62    */
     63   public SimpleTimeLimiter(ExecutorService executor) {
     64     checkNotNull(executor);
     65     this.executor = executor;
     66   }
     67 
     68   /**
     69    * Constructs a TimeLimiter instance using a {@link
     70    * java.util.concurrent.Executors#newCachedThreadPool()} to execute proxied
     71    * method calls.
     72    *
     73    * <p><b>Warning:</b> using a bounded executor may be counterproductive! If
     74    * the thread pool fills up, any time callers spend waiting for a thread may
     75    * count toward their time limit, and in this case the call may even time out
     76    * before the target method is ever invoked.
     77    */
     78   public SimpleTimeLimiter() {
     79     this(Executors.newCachedThreadPool());
     80   }
     81 
     82   public <T> T newProxy(final T target, Class<T> interfaceType,
     83       final long timeoutDuration, final TimeUnit timeoutUnit) {
     84     checkNotNull(target);
     85     checkNotNull(interfaceType);
     86     checkNotNull(timeoutUnit);
     87     checkArgument(timeoutDuration > 0, "bad timeout: " + timeoutDuration);
     88     checkArgument(interfaceType.isInterface(),
     89         "interfaceType must be an interface type");
     90 
     91     final Set<Method> interruptibleMethods
     92         = findInterruptibleMethods(interfaceType);
     93 
     94     InvocationHandler handler = new InvocationHandler() {
     95       public Object invoke(Object obj, final Method method, final Object[] args)
     96           throws Throwable {
     97         Callable<Object> callable = new Callable<Object>() {
     98           public Object call() throws Exception {
     99             try {
    100               return method.invoke(target, args);
    101             } catch (InvocationTargetException e) {
    102               Throwables.throwCause(e, false);
    103               throw new AssertionError("can't get here");
    104             }
    105           }
    106         };
    107         return callWithTimeout(callable, timeoutDuration, timeoutUnit,
    108             interruptibleMethods.contains(method));
    109       }
    110     };
    111     return newProxy(interfaceType, handler);
    112   }
    113 
    114   // TODO: should this actually throw only ExecutionException?
    115   public <T> T callWithTimeout(Callable<T> callable, long timeoutDuration,
    116       TimeUnit timeoutUnit, boolean amInterruptible) throws Exception {
    117     checkNotNull(callable);
    118     checkNotNull(timeoutUnit);
    119     checkArgument(timeoutDuration > 0, "bad timeout: " + timeoutDuration);
    120     Future<T> future = executor.submit(callable);
    121     try {
    122       if (amInterruptible) {
    123         try {
    124           return future.get(timeoutDuration, timeoutUnit);
    125         } catch (InterruptedException e) {
    126           future.cancel(true);
    127           throw e;
    128         }
    129       } else {
    130         Future<T> uninterruptible = Futures.makeUninterruptible(future);
    131         return uninterruptible.get(timeoutDuration, timeoutUnit);
    132       }
    133     } catch (ExecutionException e) {
    134       throw Throwables.throwCause(e, true);
    135     } catch (TimeoutException e) {
    136       future.cancel(true);
    137       throw new UncheckedTimeoutException(e);
    138     }
    139   }
    140 
    141   private static Set<Method> findInterruptibleMethods(Class<?> interfaceType) {
    142     Set<Method> set = Sets.newHashSet();
    143     for (Method m : interfaceType.getMethods()) {
    144       if (declaresInterruptedEx(m)) {
    145         set.add(m);
    146       }
    147     }
    148     return set;
    149   }
    150 
    151   private static boolean declaresInterruptedEx(Method method) {
    152     for (Class<?> exType : method.getExceptionTypes()) {
    153       // debate: == or isAssignableFrom?
    154       if (exType == InterruptedException.class) {
    155         return true;
    156       }
    157     }
    158     return false;
    159   }
    160 
    161   // TODO: replace with version in common.reflect if and when that gets open-sourced
    162   private static <T> T newProxy(
    163       Class<T> interfaceType, InvocationHandler handler) {
    164     Object object = Proxy.newProxyInstance(
    165         interfaceType.getClassLoader(), new Class<?>[] { interfaceType }, handler);
    166     return interfaceType.cast(object);
    167   }
    168 }
    169