/* * Copyright 2011 The Netty Project * * The Netty Project 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.jboss.netty.channel; import static java.util.concurrent.TimeUnit.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.util.internal.DeadLockProofWorker; /** * The default {@link ChannelFuture} implementation. It is recommended to * use {@link Channels#future(Channel)} and {@link Channels#future(Channel, boolean)} * to create a new {@link ChannelFuture} rather than calling the constructor * explicitly. */ public class DefaultChannelFuture implements ChannelFuture { private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelFuture.class); private static final Throwable CANCELLED = new Throwable(); private static volatile boolean useDeadLockChecker = true; private static boolean disabledDeadLockCheckerOnce; /** * Returns {@code true} if and only if the dead lock checker is enabled. */ public static boolean isUseDeadLockChecker() { return useDeadLockChecker; } /** * Enables or disables the dead lock checker. It is not recommended to * disable the dead lock checker. Disable it at your own risk! */ public static void setUseDeadLockChecker(boolean useDeadLockChecker) { if (!useDeadLockChecker && !disabledDeadLockCheckerOnce) { disabledDeadLockCheckerOnce = true; if (logger.isDebugEnabled()) { logger.debug( "The dead lock checker in " + DefaultChannelFuture.class.getSimpleName() + " has been disabled as requested at your own risk."); } } DefaultChannelFuture.useDeadLockChecker = useDeadLockChecker; } private final Channel channel; private final boolean cancellable; private ChannelFutureListener firstListener; private List<ChannelFutureListener> otherListeners; private List<ChannelFutureProgressListener> progressListeners; private boolean done; private Throwable cause; private int waiters; /** * Creates a new instance. * * @param channel * the {@link Channel} associated with this future * @param cancellable * {@code true} if and only if this future can be canceled */ public DefaultChannelFuture(Channel channel, boolean cancellable) { this.channel = channel; this.cancellable = cancellable; } public Channel getChannel() { return channel; } public synchronized boolean isDone() { return done; } public synchronized boolean isSuccess() { return done && cause == null; } public synchronized Throwable getCause() { if (cause != CANCELLED) { return cause; } else { return null; } } public synchronized boolean isCancelled() { return cause == CANCELLED; } public void addListener(ChannelFutureListener listener) { if (listener == null) { throw new NullPointerException("listener"); } boolean notifyNow = false; synchronized (this) { if (done) { notifyNow = true; } else { if (firstListener == null) { firstListener = listener; } else { if (otherListeners == null) { otherListeners = new ArrayList<ChannelFutureListener>(1); } otherListeners.add(listener); } if (listener instanceof ChannelFutureProgressListener) { if (progressListeners == null) { progressListeners = new ArrayList<ChannelFutureProgressListener>(1); } progressListeners.add((ChannelFutureProgressListener) listener); } } } if (notifyNow) { notifyListener(listener); } } public void removeListener(ChannelFutureListener listener) { if (listener == null) { throw new NullPointerException("listener"); } synchronized (this) { if (!done) { if (listener == firstListener) { if (otherListeners != null && !otherListeners.isEmpty()) { firstListener = otherListeners.remove(0); } else { firstListener = null; } } else if (otherListeners != null) { otherListeners.remove(listener); } if (listener instanceof ChannelFutureProgressListener) { progressListeners.remove(listener); } } } } public ChannelFuture rethrowIfFailed() throws Exception { if (!isDone()) { return this; } Throwable cause = getCause(); if (cause == null) { return this; } if (cause instanceof Exception) { throw (Exception) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException(cause); } public ChannelFuture await() throws InterruptedException { if (Thread.interrupted()) { throw new InterruptedException(); } synchronized (this) { while (!done) { checkDeadLock(); waiters++; try { this.wait(); } finally { waiters--; } } } return this; } public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return await0(unit.toNanos(timeout), true); } public boolean await(long timeoutMillis) throws InterruptedException { return await0(MILLISECONDS.toNanos(timeoutMillis), true); } public ChannelFuture awaitUninterruptibly() { boolean interrupted = false; synchronized (this) { while (!done) { checkDeadLock(); waiters++; try { this.wait(); } catch (InterruptedException e) { interrupted = true; } finally { waiters--; } } } if (interrupted) { Thread.currentThread().interrupt(); } return this; } public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { try { return await0(unit.toNanos(timeout), false); } catch (InterruptedException e) { throw new InternalError(); } } public boolean awaitUninterruptibly(long timeoutMillis) { try { return await0(MILLISECONDS.toNanos(timeoutMillis), false); } catch (InterruptedException e) { throw new InternalError(); } } private boolean await0(long timeoutNanos, boolean interruptable) throws InterruptedException { if (interruptable && Thread.interrupted()) { throw new InterruptedException(); } long startTime = timeoutNanos <= 0 ? 0 : System.nanoTime(); long waitTime = timeoutNanos; boolean interrupted = false; try { synchronized (this) { if (done) { return done; } else if (waitTime <= 0) { return done; } checkDeadLock(); waiters++; try { for (;;) { try { this.wait(waitTime / 1000000, (int) (waitTime % 1000000)); } catch (InterruptedException e) { if (interruptable) { throw e; } else { interrupted = true; } } if (done) { return true; } else { waitTime = timeoutNanos - (System.nanoTime() - startTime); if (waitTime <= 0) { return done; } } } } finally { waiters--; } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } private void checkDeadLock() { if (isUseDeadLockChecker() && DeadLockProofWorker.PARENT.get() != null) { throw new IllegalStateException( "await*() in I/O thread causes a dead lock or " + "sudden performance drop. Use addListener() instead or " + "call await*() from a different thread."); } } public boolean setSuccess() { synchronized (this) { // Allow only once. if (done) { return false; } done = true; if (waiters > 0) { notifyAll(); } } notifyListeners(); return true; } public boolean setFailure(Throwable cause) { synchronized (this) { // Allow only once. if (done) { return false; } this.cause = cause; done = true; if (waiters > 0) { notifyAll(); } } notifyListeners(); return true; } public boolean cancel() { if (!cancellable) { return false; } synchronized (this) { // Allow only once. if (done) { return false; } cause = CANCELLED; done = true; if (waiters > 0) { notifyAll(); } } notifyListeners(); return true; } private void notifyListeners() { // 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() if (firstListener != null) { notifyListener(firstListener); firstListener = null; if (otherListeners != null) { for (ChannelFutureListener l: otherListeners) { notifyListener(l); } otherListeners = null; } } } private void notifyListener(ChannelFutureListener l) { try { l.operationComplete(this); } catch (Throwable t) { if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by " + ChannelFutureListener.class.getSimpleName() + ".", t); } } } public boolean setProgress(long amount, long current, long total) { ChannelFutureProgressListener[] plisteners; synchronized (this) { // Do not generate progress event after completion. if (done) { return false; } Collection<ChannelFutureProgressListener> progressListeners = this.progressListeners; if (progressListeners == null || progressListeners.isEmpty()) { // Nothing to notify - no need to create an empty array. return true; } plisteners = progressListeners.toArray( new ChannelFutureProgressListener[progressListeners.size()]); } for (ChannelFutureProgressListener pl: plisteners) { notifyProgressListener(pl, amount, current, total); } return true; } private void notifyProgressListener( ChannelFutureProgressListener l, long amount, long current, long total) { try { l.operationProgressed(this, amount, current, total); } catch (Throwable t) { if (logger.isWarnEnabled()) { logger.warn( "An exception was thrown by " + ChannelFutureProgressListener.class.getSimpleName() + ".", t); } } } }