All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.util.concurrent.TimeoutFuture Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2006 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.util.concurrent;

import static com.google.common.util.concurrent.MoreExecutors.directExecutor;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Preconditions;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;

/**
 * Implementation of {@code Futures#withTimeout}.
 *
 * 

Future that delegates to another but will finish early (via a {@link TimeoutException} wrapped * in an {@link ExecutionException}) if the specified duration expires. The delegate future is * interrupted and cancelled if it times out. */ @GwtIncompatible final class TimeoutFuture extends AbstractFuture.TrustedFuture { static ListenableFuture create( ListenableFuture delegate, long time, TimeUnit unit, ScheduledExecutorService scheduledExecutor) { TimeoutFuture result = new TimeoutFuture(delegate); TimeoutFuture.Fire fire = new TimeoutFuture.Fire(result); result.timer = scheduledExecutor.schedule(fire, time, unit); delegate.addListener(fire, directExecutor()); return result; } /* * Memory visibility of these fields. There are two cases to consider. * * 1. visibility of the writes to these fields to Fire.run: * * The initial write to delegateRef is made definitely visible via the semantics of * addListener/SES.schedule. The later racy write in cancel() is not guaranteed to be observed, * however that is fine since the correctness is based on the atomic state in our base class. The * initial write to timer is never definitely visible to Fire.run since it is assigned after * SES.schedule is called. Therefore Fire.run has to check for null. However, it should be visible * if Fire.run is called by delegate.addListener since addListener is called after the assignment * to timer, and importantly this is the main situation in which we need to be able to see the * write. * * 2. visibility of the writes to an afterDone() call triggered by cancel(): * * Since these fields are non-final that means that TimeoutFuture is not being 'safely published', * thus a motivated caller may be able to expose the reference to another thread that would then * call cancel() and be unable to cancel the delegate. * There are a number of ways to solve this, none of which are very pretty, and it is currently * believed to be a purely theoretical problem (since the other actions should supply sufficient * write-barriers). */ @Nullable private ListenableFuture delegateRef; @Nullable private Future timer; private TimeoutFuture(ListenableFuture delegate) { this.delegateRef = Preconditions.checkNotNull(delegate); } /** A runnable that is called when the delegate or the timer completes. */ private static final class Fire implements Runnable { @Nullable TimeoutFuture timeoutFutureRef; Fire(TimeoutFuture timeoutFuture) { this.timeoutFutureRef = timeoutFuture; } @Override public void run() { // If either of these reads return null then we must be after a successful cancel or another // call to this method. TimeoutFuture timeoutFuture = timeoutFutureRef; if (timeoutFuture == null) { return; } ListenableFuture delegate = timeoutFuture.delegateRef; if (delegate == null) { return; } /* * If we're about to complete the TimeoutFuture, we want to release our reference to it. * Otherwise, we'll pin it (and its result) in memory until the timeout task is GCed. (The * need to clear our reference to the TimeoutFuture is the reason we use a *static* nested * class with a manual reference back to the "containing" class.) * * This has the nice-ish side effect of limiting reentrancy: run() calls * timeoutFuture.setException() calls run(). That reentrancy would already be harmless, since * timeoutFuture can be set (and delegate cancelled) only once. (And "set only once" is * important for other reasons: run() can still be invoked concurrently in different threads, * even with the above null checks.) */ timeoutFutureRef = null; if (delegate.isDone()) { timeoutFuture.setFuture(delegate); } else { try { // TODO(lukes): this stack trace is particularly useless (all it does is point at the // scheduledexecutorservice thread), consider eliminating it altogether? timeoutFuture.setException(new TimeoutException("Future timed out: " + delegate)); } finally { delegate.cancel(true); } } } } @Override protected void afterDone() { maybePropagateCancellation(delegateRef); Future localTimer = timer; // Try to cancel the timer as an optimization. // timer may be null if this call to run was by the timer task since there is no happens-before // edge between the assignment to timer and an execution of the timer task. if (localTimer != null) { localTimer.cancel(false); } delegateRef = null; timer = null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy