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