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 117 <T> T callWithTimeout( 118 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit, boolean amInterruptible) 119 throws Exception { 120 checkNotNull(callable); 121 checkNotNull(timeoutUnit); 122 checkPositiveTimeout(timeoutDuration); 123 124 Future<T> future = executor.submit(callable); 125 126 try { 127 if (amInterruptible) { 128 try { 129 return future.get(timeoutDuration, timeoutUnit); 130 } catch (InterruptedException e) { 131 future.cancel(true); 132 throw e; 133 } 134 } else { 135 return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 136 } 137 } catch (ExecutionException e) { 138 throw throwCause(e, true /* combineStackTraces */); 139 } catch (TimeoutException e) { 140 future.cancel(true); 141 throw new UncheckedTimeoutException(e); 142 } 143 } 144 145 @CanIgnoreReturnValue 146 @Override 147 public <T> T callWithTimeout(Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit) 148 throws TimeoutException, InterruptedException, ExecutionException { 149 checkNotNull(callable); 150 checkNotNull(timeoutUnit); 151 checkPositiveTimeout(timeoutDuration); 152 153 Future<T> future = executor.submit(callable); 154 155 try { 156 return future.get(timeoutDuration, timeoutUnit); 157 } catch (InterruptedException | TimeoutException e) { 158 future.cancel(true /* mayInterruptIfRunning */); 159 throw e; 160 } catch (ExecutionException e) { 161 wrapAndThrowExecutionExceptionOrError(e.getCause()); 162 throw new AssertionError(); 163 } 164 } 165 166 @CanIgnoreReturnValue 167 @Override 168 public <T> T callUninterruptiblyWithTimeout( 169 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit) 170 throws TimeoutException, ExecutionException { 171 checkNotNull(callable); 172 checkNotNull(timeoutUnit); 173 checkPositiveTimeout(timeoutDuration); 174 175 Future<T> future = executor.submit(callable); 176 177 try { 178 return Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 179 } catch (TimeoutException e) { 180 future.cancel(true /* mayInterruptIfRunning */); 181 throw e; 182 } catch (ExecutionException e) { 183 wrapAndThrowExecutionExceptionOrError(e.getCause()); 184 throw new AssertionError(); 185 } 186 } 187 188 @Override 189 public void runWithTimeout(Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) 190 throws TimeoutException, InterruptedException { 191 checkNotNull(runnable); 192 checkNotNull(timeoutUnit); 193 checkPositiveTimeout(timeoutDuration); 194 195 Future<?> future = executor.submit(runnable); 196 197 try { 198 future.get(timeoutDuration, timeoutUnit); 199 } catch (InterruptedException | TimeoutException e) { 200 future.cancel(true /* mayInterruptIfRunning */); 201 throw e; 202 } catch (ExecutionException e) { 203 wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); 204 throw new AssertionError(); 205 } 206 } 207 208 @Override 209 public void runUninterruptiblyWithTimeout( 210 Runnable runnable, long timeoutDuration, TimeUnit timeoutUnit) throws TimeoutException { 211 checkNotNull(runnable); 212 checkNotNull(timeoutUnit); 213 checkPositiveTimeout(timeoutDuration); 214 215 Future<?> future = executor.submit(runnable); 216 217 try { 218 Uninterruptibles.getUninterruptibly(future, timeoutDuration, timeoutUnit); 219 } catch (TimeoutException e) { 220 future.cancel(true /* mayInterruptIfRunning */); 221 throw e; 222 } catch (ExecutionException e) { 223 wrapAndThrowRuntimeExecutionExceptionOrError(e.getCause()); 224 throw new AssertionError(); 225 } 226 } 227 228 private static Exception throwCause(Exception e, boolean combineStackTraces) throws Exception { 229 Throwable cause = e.getCause(); 230 if (cause == null) { 231 throw e; 232 } 233 if (combineStackTraces) { 234 StackTraceElement[] combined = 235 ObjectArrays.concat(cause.getStackTrace(), e.getStackTrace(), StackTraceElement.class); 236 cause.setStackTrace(combined); 237 } 238 if (cause instanceof Exception) { 239 throw (Exception) cause; 240 } 241 if (cause instanceof Error) { 242 throw (Error) cause; 243 } 244 // The cause is a weird kind of Throwable, so throw the outer exception. 245 throw e; 246 } 247 248 private static Set<Method> findInterruptibleMethods(Class<?> interfaceType) { 249 Set<Method> set = Sets.newHashSet(); 250 for (Method m : interfaceType.getMethods()) { 251 if (declaresInterruptedEx(m)) { 252 set.add(m); 253 } 254 } 255 return set; 256 } 257 258 private static boolean declaresInterruptedEx(Method method) { 259 for (Class<?> exType : method.getExceptionTypes()) { 260 // debate: == or isAssignableFrom? 261 if (exType == InterruptedException.class) { 262 return true; 263 } 264 } 265 return false; 266 } 267 268 private void wrapAndThrowExecutionExceptionOrError(Throwable cause) throws ExecutionException { 269 if (cause instanceof Error) { 270 throw new ExecutionError((Error) cause); 271 } else if (cause instanceof RuntimeException) { 272 throw new UncheckedExecutionException(cause); 273 } else { 274 throw new ExecutionException(cause); 275 } 276 } 277 278 private void wrapAndThrowRuntimeExecutionExceptionOrError(Throwable cause) { 279 if (cause instanceof Error) { 280 throw new ExecutionError((Error) cause); 281 } else { 282 throw new UncheckedExecutionException(cause); 283 } 284 } 285 286 private static void checkPositiveTimeout(long timeoutDuration) { 287 checkArgument(timeoutDuration > 0, "timeout must be positive: %s", timeoutDuration); 288 } 289}