001/* 002 * Copyright (C) 2006 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.util.concurrent; 016 017import static com.google.common.base.Preconditions.checkArgument; 018import static com.google.common.base.Preconditions.checkNotNull; 019 020import com.google.common.annotations.Beta; 021import com.google.common.annotations.GwtIncompatible; 022import com.google.common.collect.ObjectArrays; 023import com.google.common.collect.Sets; 024import com.google.errorprone.annotations.CanIgnoreReturnValue; 025import java.lang.reflect.InvocationHandler; 026import java.lang.reflect.InvocationTargetException; 027import java.lang.reflect.Method; 028import java.lang.reflect.Proxy; 029import java.util.Set; 030import java.util.concurrent.Callable; 031import java.util.concurrent.ExecutionException; 032import java.util.concurrent.ExecutorService; 033import java.util.concurrent.Executors; 034import java.util.concurrent.Future; 035import java.util.concurrent.TimeUnit; 036import java.util.concurrent.TimeoutException; 037 038/** 039 * A TimeLimiter that runs method calls in the background using an {@link ExecutorService}. If the 040 * time limit expires for a given method call, the thread running the call will be interrupted. 041 * 042 * @author Kevin Bourrillion 043 * @author Jens Nyman 044 * @since 1.0 045 */ 046@Beta 047@GwtIncompatible 048public final class SimpleTimeLimiter implements TimeLimiter { 049 050 private final ExecutorService executor; 051 052 /** 053 * Constructs a TimeLimiter instance using the given executor service to execute proxied method 054 * calls. 055 * 056 * <p><b>Warning:</b> using a bounded executor may be counterproductive! If the thread pool fills 057 * up, any time callers spend waiting for a thread may count toward their time limit, and in this 058 * case the call may even time out before the target method is ever invoked. 059 * 060 * @param executor the ExecutorService that will execute the method calls on the target objects; 061 * for example, a {@link Executors#newCachedThreadPool()}. 062 * @deprecated Use {@link #create(ExecutorService)} instead. This method is scheduled to be 063 * removed in Guava 23.0. 064 */ 065 @Deprecated 066 public SimpleTimeLimiter(ExecutorService executor) { 067 this.executor = checkNotNull(executor); 068 } 069 070 /** 071 * Constructs a TimeLimiter instance using a {@link Executors#newCachedThreadPool()} to execute 072 * proxied method calls. 073 * 074 * <p><b>Warning:</b> using a bounded executor may be counterproductive! If the thread pool fills 075 * up, any time callers spend waiting for a thread may count toward their time limit, and in this 076 * case the call may even time out before the target method is ever invoked. 077 * 078 * @deprecated Use {@link #create(ExecutorService)} instead with {@code 079 * Executors.newCachedThreadPool()}. This method is scheduled to be removed in Guava 23.0. 080 */ 081 @Deprecated 082 public SimpleTimeLimiter() { 083 this(Executors.newCachedThreadPool()); 084 } 085 086 /** 087 * Creates a TimeLimiter instance using the given executor service to execute method calls. 088 * 089 * <p><b>Warning:</b> using a bounded executor may be counterproductive! If the thread pool fills 090 * up, any time callers spend waiting for a thread may count toward their time limit, and in this 091 * case the call may even time out before the target method is ever invoked. 092 * 093 * @param executor the ExecutorService that will execute the method calls on the target objects; 094 * for example, a {@link Executors#newCachedThreadPool()}. 095 * @since 22.0 096 */ 097 public static SimpleTimeLimiter create(ExecutorService executor) { 098 return new SimpleTimeLimiter(executor); 099 } 100 101 @Override 102 public <T> T newProxy( 103 final T target, 104 Class<T> interfaceType, 105 final long timeoutDuration, 106 final TimeUnit timeoutUnit) { 107 checkNotNull(target); 108 checkNotNull(interfaceType); 109 checkNotNull(timeoutUnit); 110 checkPositiveTimeout(timeoutDuration); 111 checkArgument(interfaceType.isInterface(), "interfaceType must be an interface type"); 112 113 final Set<Method> interruptibleMethods = findInterruptibleMethods(interfaceType); 114 115 InvocationHandler handler = 116 new InvocationHandler() { 117 @Override 118 public Object invoke(Object obj, final Method method, final Object[] args) 119 throws Throwable { 120 Callable<Object> callable = 121 new Callable<Object>() { 122 @Override 123 public Object call() throws Exception { 124 try { 125 return method.invoke(target, args); 126 } catch (InvocationTargetException e) { 127 throw throwCause(e, false /* combineStackTraces */); 128 } 129 } 130 }; 131 return callWithTimeout( 132 callable, timeoutDuration, timeoutUnit, interruptibleMethods.contains(method)); 133 } 134 }; 135 return newProxy(interfaceType, handler); 136 } 137 138 // TODO: should this actually throw only ExecutionException? 139 @Deprecated 140 @CanIgnoreReturnValue 141 @Override 142 public <T> T callWithTimeout( 143 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit, boolean amInterruptible) 144 throws Exception { 145 checkNotNull(callable); 146 checkNotNull(timeoutUnit); 147 checkPositiveTimeout(timeoutDuration); 148 149 Future<T> future = executor.submit(callable); 150 151 try { 152 if (amInterruptible) { 153 try { 154 return future.get(timeoutDuration, timeoutUnit); 155 } catch (InterruptedException e) { 156 future.cancel(true); 157 throw e; 158 } 159 } else { 160 return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 161 } 162 } catch (ExecutionException e) { 163 throw throwCause(e, true /* combineStackTraces */); 164 } catch (TimeoutException e) { 165 future.cancel(true); 166 throw new UncheckedTimeoutException(e); 167 } 168 } 169 170 @Override 171 public <T> T callWithTimeout(Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit) 172 throws TimeoutException, InterruptedException, ExecutionException { 173 checkNotNull(callable); 174 checkNotNull(timeoutUnit); 175 checkPositiveTimeout(timeoutDuration); 176 177 Future<T> future = executor.submit(callable); 178 179 try { 180 return future.get(timeoutDuration, timeoutUnit); 181 } catch (InterruptedException | TimeoutException e) { 182 future.cancel(true /* mayInterruptIfRunning */); 183 throw e; 184 } catch (ExecutionException e) { 185 wrapAndThrowExecutionExceptionOrError(e.getCause()); 186 throw new AssertionError(); 187 } 188 } 189 190 @Override 191 public <T> T callUninterruptiblyWithTimeout( 192 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit) 193 throws TimeoutException, ExecutionException { 194 checkNotNull(callable); 195 checkNotNull(timeoutUnit); 196 checkPositiveTimeout(timeoutDuration); 197 198 Future<T> future = executor.submit(callable); 199 200 try { 201 return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 202 } catch (TimeoutException e) { 203 future.cancel(true /* mayInterruptIfRunning */); 204 throw e; 205 } catch (ExecutionException e) { 206 wrapAndThrowExecutionExceptionOrError(e.getCause()); 207 throw new AssertionError(); 208 } 209 } 210 211 @Override 212 public void runWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) 213 throws TimeoutException, InterruptedException { 214 checkNotNull(runnable); 215 checkNotNull(timeoutUnit); 216 checkPositiveTimeout(timeoutDuration); 217 218 Future<?> future = executor.submit(runnable); 219 220 try { 221 future.get(timeoutDuration, timeoutUnit); 222 } catch (InterruptedException | TimeoutException e) { 223 future.cancel(true /* mayInterruptIfRunning */); 224 throw e; 225 } catch (ExecutionException e) { 226 wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); 227 throw new AssertionError(); 228 } 229 } 230 231 @Override 232 public void runUninterruptiblyWithTimeout( 233 Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) throws TimeoutException { 234 checkNotNull(runnable); 235 checkNotNull(timeoutUnit); 236 checkPositiveTimeout(timeoutDuration); 237 238 Future<?> future = executor.submit(runnable); 239 240 try { 241 Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 242 } catch (TimeoutException e) { 243 future.cancel(true /* mayInterruptIfRunning */); 244 throw e; 245 } catch (ExecutionException e) { 246 wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); 247 throw new AssertionError(); 248 } 249 } 250 251 private static Exception throwCause(Exception e, boolean combineStackTraces) throws Exception { 252 Throwable cause = e.getCause(); 253 if (cause == null) { 254 throw e; 255 } 256 if (combineStackTraces) { 257 StackTraceElement[] combined = 258 ObjectArrays.concat(cause.getStackTrace(), e.getStackTrace(), StackTraceElement.class); 259 cause.setStackTrace(combined); 260 } 261 if (cause instanceof Exception) { 262 throw (Exception) cause; 263 } 264 if (cause instanceof Error) { 265 throw (Error) cause; 266 } 267 // The cause is a weird kind of Throwable, so throw the outer exception. 268 throw e; 269 } 270 271 private static Set<Method> findInterruptibleMethods(Class<?> interfaceType) { 272 Set<Method> set = Sets.newHashSet(); 273 for (Method m : interfaceType.getMethods()) { 274 if (declaresInterruptedEx(m)) { 275 set.add(m); 276 } 277 } 278 return set; 279 } 280 281 private static boolean declaresInterruptedEx(Method method) { 282 for (Class<?> exType : method.getExceptionTypes()) { 283 // debate: == or isAssignableFrom? 284 if (exType == InterruptedException.class) { 285 return true; 286 } 287 } 288 return false; 289 } 290 291 // TODO: replace with version in common.reflect if and when it's open-sourced 292 private static <T> T newProxy(Class<T> interfaceType, InvocationHandler handler) { 293 Object object = 294 Proxy.newProxyInstance( 295 interfaceType.getClassLoader(), new Class<?>[] {interfaceType}, handler); 296 return interfaceType.cast(object); 297 } 298 299 private void wrapAndThrowExecutionExceptionOrError(Throwable cause) throws ExecutionException { 300 if (cause instanceof Error) { 301 throw new ExecutionError((Error) cause); 302 } else if (cause instanceof RuntimeException) { 303 throw new UncheckedExecutionException(cause); 304 } else { 305 throw new ExecutionException(cause); 306 } 307 } 308 309 private void wrapAndThrowRuntimeExecutionExceptionOrError(Throwable cause) { 310 if (cause instanceof Error) { 311 throw new ExecutionError((Error) cause); 312 } else { 313 throw new UncheckedExecutionException(cause); 314 } 315 } 316 317 private static void checkPositiveTimeout(long timeoutDuration) { 318 checkArgument(timeoutDuration > 0, "timeout must be positive: %s", timeoutDuration); 319 } 320}