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// TODO: b/227335009 - Maybe change interruption behavior, but it requires thought. 052@SuppressWarnings("Interruption") 053public final class SimpleTimeLimiter implements TimeLimiter { 054 055 private final ExecutorService executor; 056 057 private SimpleTimeLimiter(ExecutorService executor) { 058 this.executor = checkNotNull(executor); 059 } 060 061 /** 062 * Creates a TimeLimiter instance using the given executor service to execute method calls. 063 * 064 * <p><b>Warning:</b> using a bounded executor may be counterproductive! If the thread pool fills 065 * up, any time callers spend waiting for a thread may count toward their time limit, and in this 066 * case the call may even time out before the target method is ever invoked. 067 * 068 * @param executor the ExecutorService that will execute the method calls on the target objects; 069 * for example, a {@link Executors#newCachedThreadPool()}. 070 * @since 22.0 071 */ 072 public static SimpleTimeLimiter create(ExecutorService executor) { 073 return new SimpleTimeLimiter(executor); 074 } 075 076 @Override 077 public <T> T newProxy( 078 T target, Class<T> interfaceType, long timeoutDuration, TimeUnit timeoutUnit) { 079 checkNotNull(target); 080 checkNotNull(interfaceType); 081 checkNotNull(timeoutUnit); 082 checkPositiveTimeout(timeoutDuration); 083 checkArgument(interfaceType.isInterface(), "interfaceType must be an interface type"); 084 085 Set<Method> interruptibleMethods = findInterruptibleMethods(interfaceType); 086 087 InvocationHandler handler = 088 new InvocationHandler() { 089 @Override 090 @CheckForNull 091 public Object invoke(Object obj, Method method, @CheckForNull @Nullable Object[] args) 092 throws Throwable { 093 Callable<@Nullable Object> callable = 094 () -> { 095 try { 096 return method.invoke(target, args); 097 } catch (InvocationTargetException e) { 098 throw throwCause(e, false /* combineStackTraces */); 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 @ParametricNullness 117 private <T extends @Nullable Object> 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 return amInterruptible 128 ? future.get(timeoutDuration, timeoutUnit) 129 : getUninterruptibly(future, timeoutDuration, timeoutUnit); 130 } catch (InterruptedException e) { 131 future.cancel(true); 132 throw e; 133 } catch (ExecutionException e) { 134 throw throwCause(e, true /* combineStackTraces */); 135 } catch (TimeoutException e) { 136 future.cancel(true); 137 throw new UncheckedTimeoutException(e); 138 } 139 } 140 141 @CanIgnoreReturnValue 142 @Override 143 @ParametricNullness 144 public <T extends @Nullable Object> T callWithTimeout( 145 Callable<T> callable, long timeoutDuration, TimeUnit timeoutUnit) 146 throws TimeoutException, InterruptedException, ExecutionException { 147 checkNotNull(callable); 148 checkNotNull(timeoutUnit); 149 checkPositiveTimeout(timeoutDuration); 150 151 Future<T> future = executor.submit(callable); 152 153 try { 154 return future.get(timeoutDuration, timeoutUnit); 155 } catch (InterruptedException | TimeoutException e) { 156 future.cancel(true /* mayInterruptIfRunning */); 157 throw e; 158 } catch (ExecutionException e) { 159 wrapAndThrowExecutionExceptionOrError(e.getCause()); 160 throw new AssertionError(); 161 } 162 } 163 164 @CanIgnoreReturnValue 165 @Override 166 @ParametricNullness 167 public <T extends @Nullable Object> 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 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 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}