/* * Copyright 2009 Thomas Bocek * * 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 net.tomp2p.futures; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import net.tomp2p.connection.ConnectionBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The base for all BaseFuture implementations. Be aware of possible deadlocks. Never await from a listener. This class * is heavily inspired by MINA and Netty. * * @param <K> * The class that extends BaseFuture and is used to return back the type for method calls. E.g, if K is * FutureDHT await() returns FutureDHT. * @author Thomas Bocek */ public abstract class BaseFutureImpl<K extends BaseFuture> implements BaseFuture { private static final Logger LOG = LoggerFactory.getLogger(BaseFutureImpl.class); // Listeners that gets notified if the future finished private final List<BaseFutureListener<? extends BaseFuture>> listeners = new ArrayList<BaseFutureListener<? extends BaseFuture>>(1); // While a future is running, the process may add cancellations for faster // cancel operations, e.g. cancel connection attempt private volatile Cancel cancel = null; private final CountDownLatch listenersFinished = new CountDownLatch(1); protected final Object lock; // set the ready flag if operation completed protected boolean completed = false; // by default false, change in case of success. An unfinished operation is // always set to failed protected FutureType type = FutureType.INIT; protected String reason = "unknown"; private K self; /** * Default constructor that sets the lock object, which is used for synchronization to this instance. */ public BaseFutureImpl() { this.lock = this; } /** * @param self2 * Set the type so that we are able to return it to the user. This is for making the API much more * usable. */ protected void self(final K self2) { this.self = self2; } /** * @return The object that stored this object. This is necessary for the builder pattern when using generics. */ protected K self() { return self; } @Override public K await() throws InterruptedException { synchronized (lock) { checkDeadlock(); while (!completed) { lock.wait(); } } return self; } @Override public K awaitUninterruptibly() { synchronized (lock) { checkDeadlock(); while (!completed) { try { lock.wait(); } catch (final InterruptedException e) { LOG.debug("interrupted, but ignoring", e); } } } return self; } @Override public boolean await(final long timeoutMillis) throws InterruptedException { return await0(timeoutMillis, true); } @Override public boolean awaitUninterruptibly(final long timeoutMillis) { try { return await0(timeoutMillis, false); } catch (final InterruptedException e) { throw new RuntimeException("This should never ever happen."); } } /** * Internal await operation that also checks for potential deadlocks. * * @param timeoutMillis * The time to wait * @param interrupt * Flag to indicate if the method can throw an InterruptedException * @return True if this future has finished in timeoutMillis time, false otherwise * @throws InterruptedException * If the flag interrupt is true and this thread has been interrupted. */ private boolean await0(final long timeoutMillis, final boolean interrupt) throws InterruptedException { final long startTime = (timeoutMillis <= 0) ? 0 : System.currentTimeMillis(); long waitTime = timeoutMillis; synchronized (lock) { if (completed) { return completed; } else if (waitTime <= 0) { return completed; } checkDeadlock(); while (true) { try { lock.wait(waitTime); } catch (final InterruptedException e) { if (interrupt) { throw e; } } if (completed) { return true; } else { waitTime = timeoutMillis - (System.currentTimeMillis() - startTime); if (waitTime <= 0) { return completed; } } } } } @Override public boolean isCompleted() { synchronized (lock) { return completed; } } @Override public boolean isSuccess() { synchronized (lock) { return completed && (type == FutureType.OK); } } @Override public boolean isFailed() { synchronized (lock) { // failed means failed or canceled return completed && (type != FutureType.OK); } } @Override public boolean isCanceled() { synchronized (lock) { return completed && (type == FutureType.CANCEL); } } @Override public K failed(final BaseFuture origin) { return failed(origin.failedReason()); } @Override public K failed(final String failed, final BaseFuture origin) { StringBuilder sb = new StringBuilder(failed); return failed(sb.append(" <-> ").append(origin.failedReason()).toString()); } @Override public K failed(final Throwable t) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); t.printStackTrace(printWriter); return failed(stringWriter.toString()); } @Override public K failed(final String failed, final Throwable t) { if (t == null) { return failed("n/a"); } StringBuilder sb = new StringBuilder(failed); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); t.printStackTrace(printWriter); return failed(sb.append(" <-> ").append(stringWriter.toString()).toString()); } @Override public K failed(final String failed) { synchronized (lock) { if (!completedAndNotify()) { return self; } this.reason = failed; this.type = FutureType.FAILED; } notifyListeners(); return self; } @Override public String failedReason() { final StringBuffer sb = new StringBuffer("Future (compl/canc):"); synchronized (lock) { sb.append(completed).append("/") .append(", ").append(type.name()) .append(", ").append(reason); return sb.toString(); } } @Override public FutureType type() { synchronized (lock) { return type; } } /** * Make sure that the calling method has synchronized (lock). * * @return True if notified. It will notify if completed is not set yet. */ protected boolean completedAndNotify() { if (!completed) { completed = true; lock.notifyAll(); return true; } else { return false; } } @Override public K awaitListeners() throws InterruptedException { boolean wait = false; synchronized (lock) { checkDeadlock(); while (!completed) { lock.wait(); } if(listeners.size() > 0) { wait = true; } } if(wait) { listenersFinished.await(); } return self; } @Override public K awaitListenersUninterruptibly() { boolean wait = false; synchronized (lock) { checkDeadlock(); while (!completed) { try { lock.wait(); } catch (final InterruptedException e) { LOG.debug("interrupted, but ignoring", e); } } if(listeners.size() > 0) { wait = true; } } while(wait) { try { listenersFinished.await(); wait = false; } catch (InterruptedException e) { LOG.debug("interrupted, but ignoring", e); } } return self; } @Override public K addListener(final BaseFutureListener<? extends BaseFuture> listener) { boolean notifyNow = false; synchronized (lock) { if (completed) { notifyNow = true; } else { listeners.add(listener); } } // called only once if (notifyNow) { callOperationComplete(listener); } return self; } /** * Call operation complete or call fail listener. If the fail listener fails, its printed as a stack trace. * * @param listener * The listener to call */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void callOperationComplete(final BaseFutureListener listener) { try { listener.operationComplete(this); } catch (final Exception e) { try { listener.exceptionCaught(e); listener.operationComplete(this); } catch (final Exception e1) { LOG.error("Unexpected exception in exceptionCaught()", e1); } } } /** * If we block from a Netty thread, then the Netty thread won't receive any IO operations, thus make the user aware * of this situation. */ private void checkDeadlock() { String currentName = Thread.currentThread().getName(); if (currentName.startsWith(ConnectionBean.THREAD_NAME)) { throw new IllegalStateException("await*() in Netty I/O thread causes a dead lock or " + "sudden performance drop. Use addListener() instead or " + "call await*() from a different thread."); } } /** * Always call this from outside synchronized(lock)! */ protected void notifyListeners() { // if this is synchronized, it will deadlock, so do not lock this! // There won't be any visibility problem or concurrent modification // because 'ready' flag will be checked against both addListener and // removeListener calls. // // This method doesn't need synchronization because: // 1) This method is always called after synchronized (this) block. // Hence any listener list modification happens-before this method. // 2) This method is called only when 'done' is true. Once 'done' // becomes true, the listener list is never modified - see add/removeListener() for (final BaseFutureListener<? extends BaseFuture> listener : listeners) { callOperationComplete(listener); } listeners.clear(); listenersFinished.countDown(); // all events are one time events. It cannot happen that you get // notified twice } @Override public K removeListener(final BaseFutureListener<? extends BaseFuture> listener) { synchronized (lock) { if (!completed) { listeners.remove(listener); } } return self; } @Override public K setCancel(final Cancel cancel) { synchronized (lock) { if (!completed) { this.cancel = cancel; } } return self; } @Override public void cancel() { synchronized (lock) { if (!completedAndNotify()) { return; } this.type = FutureType.CANCEL; } if(cancel != null) { cancel.cancel(); } notifyListeners(); } }