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; 019import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly; 020 021import com.google.common.annotations.GwtIncompatible; 022import com.google.common.annotations.J2ktIncompatible; 023import com.google.common.collect.ObjectArrays; 024import com.google.common.collect.Sets; 025import com.google.errorprone.annotations.CanIgnoreReturnValue; 026import java.lang.reflect.InvocationHandler; 027import java.lang.reflect.InvocationTargetException; 028import java.lang.reflect.Method; 029import java.lang.reflect.Proxy; 030import java.util.Set; 031import java.util.concurrent.Callable; 032import java.util.concurrent.ExecutionException; 033import java.util.concurrent.ExecutorService; 034import java.util.concurrent.Executors; 035import java.util.concurrent.Future; 036import java.util.concurrent.TimeUnit; 037import java.util.concurrent.TimeoutException; 038import javax.annotation.CheckForNull; 039import org.checkerframework.checker.nullness.qual.Nullable; 040 041/** 042 * A TimeLimiter that runs method calls in the background using an {@link ExecutorService}. If the 043 * time limit expires for a given method call, the thread running the call will be interrupted. 044 * 045 * @author Kevin Bourrillion 046 * @author Jens Nyman 047 * @since 1.0 048 */ 049@J2ktIncompatible 050@GwtIncompatible 051@ElementTypesAreNonnullByDefault 052// TODO: b/227335009 - Maybe change interruption behavior, but it requires thought. 053@SuppressWarnings("Interruption") 054public final class SimpleTimeLimiter implements TimeLimiter { 055 056 private final ExecutorService executor; 057 058 private SimpleTimeLimiter(ExecutorService executor) { 059 this.executor = checkNotNull(executor); 060 } 061 062 /** 063 * Creates a TimeLimiter instance using the given executor service to execute method calls. 064 * 065 * <p><b>Warning:</b> using a bounded executor may be counterproductive! If the thread pool fills 066 * up, any time callers spend waiting for a thread may count toward their time limit, and in this 067 * case the call may even time out before the target method is ever invoked. 068 * 069 * @param executor the ExecutorService that will execute the method calls on the target objects; 070 * for example, a {@link Executors#newCachedThreadPool()}. 071 * @since 22.0 072 */ 073 public static SimpleTimeLimiter create(ExecutorService executor) { 074 return new SimpleTimeLimiter(executor); 075 } 076 077 @Override 078 public <T> T newProxy( 079 T target, Class<T> interfaceType, long timeoutDuration, TimeUnit timeoutUnit) { 080 checkNotNull(target); 081 checkNotNull(interfaceType); 082 checkNotNull(timeoutUnit); 083 checkPositiveTimeout(timeoutDuration); 084 checkArgument(interfaceType.isInterface(), "interfaceType must be an interface type"); 085 086 Set<Method> interruptibleMethods = findInterruptibleMethods(interfaceType); 087 088 InvocationHandler handler = 089 new InvocationHandler() { 090 @Override 091 @CheckForNull 092 public Object invoke(Object obj, Method method, @CheckForNull @Nullable Object[] args) 093 throws Throwable { 094 Callable<@Nullable Object> callable = 095 () -> { 096 try { 097 return method.invoke(target, args); 098 } catch (InvocationTargetException e) { 099 throw throwCause(e, false /* combineStackTraces */); 100 } 101 }; 102 return callWithTimeout( 103 callable, timeoutDuration, timeoutUnit, interruptibleMethods.contains(method)); 104 } 105 }; 106 return newProxy(interfaceType, handler); 107 } 108 109 // TODO: replace with version in common.reflect if and when it's open-sourced 110 private static <T> T newProxy(Class<T> interfaceType, InvocationHandler handler) { 111 Object object = 112 Proxy.newProxyInstance( 113 interfaceType.getClassLoader(), new Class<?>[] {interfaceType}, handler); 114 return interfaceType.cast(object); 115 } 116 117 @ParametricNullness 118 private <T extends @Nullable Object> T callWithTimeout( 119 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit, boolean amInterruptible) 120 throws Exception { 121 checkNotNull(callable); 122 checkNotNull(timeoutUnit); 123 checkPositiveTimeout(timeoutDuration); 124 125 Future<T> future = executor.submit(callable); 126 127 try { 128 return amInterruptible 129 ? future.get(timeoutDuration, timeoutUnit) 130 : getUninterruptibly(future, timeoutDuration, timeoutUnit); 131 } catch (InterruptedException e) { 132 future.cancel(true); 133 throw e; 134 } catch (ExecutionException e) { 135 throw throwCause(e, true /* combineStackTraces */); 136 } catch (TimeoutException e) { 137 future.cancel(true); 138 throw new UncheckedTimeoutException(e); 139 } 140 } 141 142 @CanIgnoreReturnValue 143 @Override 144 @ParametricNullness 145 public <T extends @Nullable Object> T callWithTimeout( 146 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 @ParametricNullness 168 public <T extends @Nullable Object> 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 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 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}