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 private SimpleTimeLimiter(ExecutorService executor) { 053 this.executor = checkNotNull(executor); 054 } 055 056 /** 057 * Creates a TimeLimiter instance using the given executor service to execute method calls. 058 * 059 * <p><b>Warning:</b> using a bounded executor may be counterproductive! If the thread pool fills 060 * up, any time callers spend waiting for a thread may count toward their time limit, and in this 061 * case the call may even time out before the target method is ever invoked. 062 * 063 * @param executor the ExecutorService that will execute the method calls on the target objects; 064 * for example, a {@link Executors#newCachedThreadPool()}. 065 * @since 22.0 066 */ 067 public static SimpleTimeLimiter create(ExecutorService executor) { 068 return new SimpleTimeLimiter(executor); 069 } 070 071 @Override 072 public <T> T newProxy( 073 final T target, 074 Class<T> interfaceType, 075 final long timeoutDuration, 076 final TimeUnit timeoutUnit) { 077 checkNotNull(target); 078 checkNotNull(interfaceType); 079 checkNotNull(timeoutUnit); 080 checkPositiveTimeout(timeoutDuration); 081 checkArgument(interfaceType.isInterface(), "interfaceType must be an interface type"); 082 083 final Set<Method> interruptibleMethods = findInterruptibleMethods(interfaceType); 084 085 InvocationHandler handler = 086 new InvocationHandler() { 087 @Override 088 public Object invoke(Object obj, final Method method, final Object[] args) 089 throws Throwable { 090 Callable<Object> callable = 091 new Callable<Object>() { 092 @Override 093 public Object call() throws Exception { 094 try { 095 return method.invoke(target, args); 096 } catch (InvocationTargetException e) { 097 throw throwCause(e, false /* combineStackTraces */); 098 } 099 } 100 }; 101 return callWithTimeout( 102 callable, timeoutDuration, timeoutUnit, interruptibleMethods.contains(method)); 103 } 104 }; 105 return newProxy(interfaceType, handler); 106 } 107 108 // TODO: replace with version in common.reflect if and when it's open-sourced 109 private static <T> T newProxy(Class<T> interfaceType, InvocationHandler handler) { 110 Object object = 111 Proxy.newProxyInstance( 112 interfaceType.getClassLoader(), new Class<?>[] {interfaceType}, handler); 113 return interfaceType.cast(object); 114 } 115 116 private <T> T callWithTimeout( 117 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit, boolean amInterruptible) 118 throws Exception { 119 checkNotNull(callable); 120 checkNotNull(timeoutUnit); 121 checkPositiveTimeout(timeoutDuration); 122 123 Future<T> future = executor.submit(callable); 124 125 try { 126 if (amInterruptible) { 127 try { 128 return future.get(timeoutDuration, timeoutUnit); 129 } catch (InterruptedException e) { 130 future.cancel(true); 131 throw e; 132 } 133 } else { 134 return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 135 } 136 } catch (ExecutionException e) { 137 throw throwCause(e, true /* combineStackTraces */); 138 } catch (TimeoutException e) { 139 future.cancel(true); 140 throw new UncheckedTimeoutException(e); 141 } 142 } 143 144 @CanIgnoreReturnValue 145 @Override 146 public <T> T callWithTimeout(Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit) 147 throws TimeoutException, InterruptedException, ExecutionException { 148 checkNotNull(callable); 149 checkNotNull(timeoutUnit); 150 checkPositiveTimeout(timeoutDuration); 151 152 Future<T> future = executor.submit(callable); 153 154 try { 155 return future.get(timeoutDuration, timeoutUnit); 156 } catch (InterruptedException | TimeoutException e) { 157 future.cancel(true /* mayInterruptIfRunning */); 158 throw e; 159 } catch (ExecutionException e) { 160 wrapAndThrowExecutionExceptionOrError(e.getCause()); 161 throw new AssertionError(); 162 } 163 } 164 165 @CanIgnoreReturnValue 166 @Override 167 public <T> T callUninterruptiblyWithTimeout( 168 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit) 169 throws TimeoutException, ExecutionException { 170 checkNotNull(callable); 171 checkNotNull(timeoutUnit); 172 checkPositiveTimeout(timeoutDuration); 173 174 Future<T> future = executor.submit(callable); 175 176 try { 177 return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 178 } catch (TimeoutException e) { 179 future.cancel(true /* mayInterruptIfRunning */); 180 throw e; 181 } catch (ExecutionException e) { 182 wrapAndThrowExecutionExceptionOrError(e.getCause()); 183 throw new AssertionError(); 184 } 185 } 186 187 @Override 188 public void runWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) 189 throws TimeoutException, InterruptedException { 190 checkNotNull(runnable); 191 checkNotNull(timeoutUnit); 192 checkPositiveTimeout(timeoutDuration); 193 194 Future<?> future = executor.submit(runnable); 195 196 try { 197 future.get(timeoutDuration, timeoutUnit); 198 } catch (InterruptedException | TimeoutException e) { 199 future.cancel(true /* mayInterruptIfRunning */); 200 throw e; 201 } catch (ExecutionException e) { 202 wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); 203 throw new AssertionError(); 204 } 205 } 206 207 @Override 208 public void runUninterruptiblyWithTimeout( 209 Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) throws TimeoutException { 210 checkNotNull(runnable); 211 checkNotNull(timeoutUnit); 212 checkPositiveTimeout(timeoutDuration); 213 214 Future<?> future = executor.submit(runnable); 215 216 try { 217 Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 218 } catch (TimeoutException e) { 219 future.cancel(true /* mayInterruptIfRunning */); 220 throw e; 221 } catch (ExecutionException e) { 222 wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); 223 throw new AssertionError(); 224 } 225 } 226 227 private static Exception throwCause(Exception e, boolean combineStackTraces) throws Exception { 228 Throwable cause = e.getCause(); 229 if (cause == null) { 230 throw e; 231 } 232 if (combineStackTraces) { 233 StackTraceElement[] combined = 234 ObjectArrays.concat(cause.getStackTrace(), e.getStackTrace(), StackTraceElement.class); 235 cause.setStackTrace(combined); 236 } 237 if (cause instanceof Exception) { 238 throw (Exception) cause; 239 } 240 if (cause instanceof Error) { 241 throw (Error) cause; 242 } 243 // The cause is a weird kind of Throwable, so throw the outer exception. 244 throw e; 245 } 246 247 private static Set<Method> findInterruptibleMethods(Class<?> interfaceType) { 248 Set<Method> set = Sets.newHashSet(); 249 for (Method m : interfaceType.getMethods()) { 250 if (declaresInterruptedEx(m)) { 251 set.add(m); 252 } 253 } 254 return set; 255 } 256 257 private static boolean declaresInterruptedEx(Method method) { 258 for (Class<?> exType : method.getExceptionTypes()) { 259 // debate: == or isAssignableFrom? 260 if (exType == InterruptedException.class) { 261 return true; 262 } 263 } 264 return false; 265 } 266 267 private void wrapAndThrowExecutionExceptionOrError(Throwable cause) throws ExecutionException { 268 if (cause instanceof Error) { 269 throw new ExecutionError((Error) cause); 270 } else if (cause instanceof RuntimeException) { 271 throw new UncheckedExecutionException(cause); 272 } else { 273 throw new ExecutionException(cause); 274 } 275 } 276 277 private void wrapAndThrowRuntimeExecutionExceptionOrError(Throwable cause) { 278 if (cause instanceof Error) { 279 throw new ExecutionError((Error) cause); 280 } else { 281 throw new UncheckedExecutionException(cause); 282 } 283 } 284 285 private static void checkPositiveTimeout(long timeoutDuration) { 286 checkArgument(timeoutDuration > 0, "timeout must be positive: %s", timeoutDuration); 287 } 288}