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

org.jclouds.concurrent.Futures Maven / Gradle / Ivy

/**
 * Licensed to jclouds, Inc. (jclouds) under one or more
 * contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  jclouds licenses this file
 * to you 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 org.jclouds.concurrent;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.collect.ForwardingObject;
import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.ForwardingFuture;
import com.google.common.util.concurrent.ListenableFuture;

/**
 * functions related to or replacing those in {@link com.google.common.util.concurrent.Futures}
 * 
 * @author Adrian Cole
 */
@Beta
public class Futures {
   @VisibleForTesting
   static class CallGetAndRunExecutionList implements Runnable {
      private final Future delegate;
      private final ExecutionList executionList;

      public CallGetAndRunExecutionList(Future delegate, ExecutionList executionList) {
         this.delegate = checkNotNull(delegate, "delegate");
         this.executionList = checkNotNull(executionList, "executionList");
      }

      @Override
      public void run() {
         try {
            delegate.get();
         } catch (InterruptedException e) {
            // This thread was interrupted. This should never happen, so we
            // throw an IllegalStateException.
            Thread.currentThread().interrupt();
            // TODO we cannot inspect the executionList at the moment to make a reasonable
            // toString()
            throw new IllegalStateException(String.format(
                     "interrupted calling get() on [%s], so could not run listeners", delegate), e);
         } catch (Throwable e) {
            // ExecutionException / CancellationException / RuntimeException
            // The task is done, run the listeners.
         }
         executionList.execute();
      }

      @Override
      public String toString() {
         return "[delegate=" + delegate + ", executionList=" + executionList + "]";
      }
   }

   // Adapted from Guava
   //
   // * to allow us to enforce supply of an adapterExecutor
   // note that this is done so that we can operate in Google AppEngine which
   // restricts thread creation
   // * to allow us to print debug info about what the delegate was doing
   public static class FutureListener {

      final ExecutorService adapterExecutor;

      // The execution list to hold our listeners.
      private final ExecutionList executionList = new ExecutionList();

      // This allows us to only start up a thread waiting on the delegate future
      // when the first listener is added.
      private final AtomicBoolean hasListeners = new AtomicBoolean(false);

      // The delegate future.
      private final Future delegate;

      static  FutureListener create(Future delegate, ExecutorService adapterExecutor) {
         return new FutureListener(delegate, adapterExecutor);
      }

      private FutureListener(Future delegate, ExecutorService adapterExecutor) {
         this.delegate = checkNotNull(delegate, "delegate");
         this.adapterExecutor = checkNotNull(adapterExecutor, "adapterExecutor");
      }

      public void addListener(Runnable listener, Executor exec) {
         executionList.add(listener, exec);

         // When a listener is first added, we run a task that will wait for
         // the delegate to finish, and when it is done will run the listeners.
         if (hasListeners.compareAndSet(false, true)) {
            if (delegate.isDone()) {
               // If the delegate is already done, run the execution list
               // immediately on the current thread.
               executionList.execute();
               return;
            }
            adapterExecutor.execute(new CallGetAndRunExecutionList(delegate, executionList));
         }
      }

      Future getFuture() {
         return delegate;
      }

      ExecutorService getExecutor() {
         return adapterExecutor;
      }
   }

   public static class ListenableFutureAdapter extends ForwardingFuture implements ListenableFuture {
      final FutureListener futureListener;

      static  ListenableFutureAdapter create(Future future, ExecutorService executor) {
         return new ListenableFutureAdapter(future, executor);
      }

      private ListenableFutureAdapter(Future future, ExecutorService executor) {
         this.futureListener = FutureListener.create(future, executor);
      }

      @Override
      protected Future delegate() {
         return futureListener.getFuture();
      }

      @Override
      public void addListener(Runnable listener, Executor exec) {
         futureListener.addListener(listener, exec);
      }

   }

   public static class LazyListenableFutureFunctionAdapter extends ForwardingObject implements
            ListenableFuture {
      private final FutureListener futureListener;
      private final Function function;

      static  LazyListenableFutureFunctionAdapter create(Future future,
               Function function, ExecutorService executor) {
         return new LazyListenableFutureFunctionAdapter(future, function, executor);
      }

      static  LazyListenableFutureFunctionAdapter create(FutureListener futureListener,
               Function function) {
         return new LazyListenableFutureFunctionAdapter(futureListener, function);
      }

      private LazyListenableFutureFunctionAdapter(Future future, Function function,
               ExecutorService executor) {
         this(FutureListener.create(future, executor), function);
      }

      private LazyListenableFutureFunctionAdapter(FutureListener futureListener,
               Function function) {
         this.futureListener = checkNotNull(futureListener, "futureListener");
         this.function = checkNotNull(function, "function");
      }

      /*
       * Concurrency detail:
       * 
       * 

To preserve the idempotency of calls to this.get(*) calls to the function are only * applied once. A lock is required to prevent multiple applications of the function. The * calls to future.get(*) are performed outside the lock, as is required to prevent calls to * get(long, TimeUnit) to persist beyond their timeout. * *

Calls to future.get(*) on every call to this.get(*) also provide the cancellation * behavior for this. * *

(Consider: in thread A, call get(), in thread B call get(long, TimeUnit). Thread B may * have to wait for Thread A to finish, which would be unacceptable.) * *

Note that each call to Future.get(*) results in a call to Future.get(*), but the * function is only applied once, so Future.get(*) is assumed to be idempotent. */ private final Object lock = new Object(); private boolean set = false; private O value = null; @Override public O get() throws InterruptedException, ExecutionException { return apply(futureListener.getFuture().get()); } @Override public O get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return apply(futureListener.getFuture().get(timeout, unit)); } private O apply(I raw) { synchronized (lock) { if (!set) { value = function.apply(raw); set = true; } return value; } } @Override public boolean cancel(boolean mayInterruptIfRunning) { return futureListener.getFuture().cancel(mayInterruptIfRunning); } @Override public boolean isCancelled() { return futureListener.getFuture().isCancelled(); } @Override public boolean isDone() { return futureListener.getFuture().isDone(); } @Override public void addListener(Runnable listener, Executor exec) { futureListener.addListener(listener, exec); } @Override protected Object delegate() { return futureListener.getFuture(); } } /** * Just like {@code Futures#compose} except that we check the type of the executorService before * creating the Future. If we are single threaded, invoke the function lazy as opposed to * chaining, so that we don't invoke get() early. */ public static ListenableFuture compose(Future future, final Function function, ExecutorService executorService) { if (future instanceof Futures.ListenableFutureAdapter) { Futures.ListenableFutureAdapter lf = (ListenableFutureAdapter) future; if (lf.futureListener.adapterExecutor.getClass().isAnnotationPresent(SingleThreaded.class)) return Futures.LazyListenableFutureFunctionAdapter.create( ((org.jclouds.concurrent.Futures.ListenableFutureAdapter) future).futureListener, function); else return com.google.common.util.concurrent.Futures.transform(lf, function, executorService); } else if (executorService.getClass().isAnnotationPresent(SingleThreaded.class)) { return Futures.LazyListenableFutureFunctionAdapter.create(future, function, executorService); } else { return com.google.common.util.concurrent.Futures.transform(Futures.makeListenable(future, executorService), function, executorService); } } /** * Just like {@code Futures#makeListenable} except that we pass in an executorService. *

* Temporary hack until http://code.google.com/p/guava-libraries/issues/detail?id=317 is fixed. */ public static ListenableFuture makeListenable(Future future, ExecutorService executorService) { if (future instanceof ListenableFuture) { return (ListenableFuture) future; } return ListenableFutureAdapter.create(future, executorService); } }