View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.core.future;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.concurrent.TimeUnit;
25  
26  import org.apache.mina.core.polling.AbstractPollingIoProcessor;
27  import org.apache.mina.core.service.IoProcessor;
28  import org.apache.mina.core.session.IoSession;
29  import org.apache.mina.util.ExceptionMonitor;
30  
31  
32  /**
33   * A default implementation of {@link IoFuture} associated with
34   * an {@link IoSession}.
35   * 
36   * @author The Apache MINA Project (dev@mina.apache.org)
37   * @version $Rev:671827 $, $Date:2008-06-26 09:49:48 +0100 (jeu., 26 juin 2008) $
38   */
39  public class DefaultIoFuture implements IoFuture {
40  
41      /** A number of seconds to wait between two deadlock controls ( 5 seconds ) */
42      private static final long DEAD_LOCK_CHECK_INTERVAL = 5000L;
43  
44      /** The associated session */
45      private final IoSession session;
46      
47      /** A lock used by the wait() method */
48      private final Object lock;
49      private IoFutureListener<?> firstListener;
50      private List<IoFutureListener<?>> otherListeners;
51      private Object result;
52      private boolean ready;
53      private int waiters;
54  
55      /**
56       * Creates a new instance associated with an {@link IoSession}.
57       *
58       * @param session an {@link IoSession} which is associated with this future
59       */
60      public DefaultIoFuture(IoSession session) {
61          this.session = session;
62          this.lock = this;
63      }
64  
65      /**
66       * {@inheritDoc}
67       */
68      public IoSession getSession() {
69          return session;
70      }
71  
72      /**
73       * @deprecated Replaced with {@link #awaitUninterruptibly()}.
74       */
75      @Deprecated
76      public void join() {
77          awaitUninterruptibly();
78      }
79  
80      /**
81       * @deprecated Replaced with {@link #awaitUninterruptibly(long)}.
82       */
83      @Deprecated
84      public boolean join(long timeoutMillis) {
85          return awaitUninterruptibly(timeoutMillis);
86      }
87  
88      /**
89       * {@inheritDoc}
90       */
91      public IoFuture await() throws InterruptedException {
92          synchronized (lock) {
93              while (!ready) {
94                  waiters++;
95                  try {
96                      // Wait for a notify, or if no notify is called,
97                      // assume that we have a deadlock and exit the 
98                      // loop to check for a potential deadlock.
99                      lock.wait(DEAD_LOCK_CHECK_INTERVAL);
100                 } finally {
101                     waiters--;
102                     if (!ready) {
103                         checkDeadLock();
104                     }
105                 }
106             }
107         }
108         return this;
109     }
110 
111     /**
112      * {@inheritDoc}
113      */
114     public boolean await(long timeout, TimeUnit unit)
115             throws InterruptedException {
116         return await(unit.toMillis(timeout));
117     }
118 
119     /**
120      * {@inheritDoc}
121      */
122     public boolean await(long timeoutMillis) throws InterruptedException {
123         return await0(timeoutMillis, true);
124     }
125 
126     /**
127      * {@inheritDoc}
128      */
129     public IoFuture awaitUninterruptibly() {
130         try {
131             await0(Long.MAX_VALUE, false);
132         } catch ( InterruptedException ie) {
133             // Do nothing : this catch is just mandatory by contract
134         }
135         
136         return this;
137     }
138 
139     /**
140      * {@inheritDoc}
141      */
142     public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
143         return awaitUninterruptibly(unit.toMillis(timeout));
144     }
145 
146     /**
147      * {@inheritDoc}
148      */
149     public boolean awaitUninterruptibly(long timeoutMillis) {
150         try {
151             return await0(timeoutMillis, false);
152         } catch (InterruptedException e) {
153             throw new InternalError();
154         }
155     }
156 
157     /**
158      * Wait for the Future to be ready. If the requested delay is 0 or 
159      * negative, this method immediately returns the value of the 
160      * 'ready' flag. 
161      * Every 5 second, the wait will be suspended to be able to check if 
162      * there is a deadlock or not.
163      * 
164      * @param timeoutMillis The delay we will wait for the Future to be ready
165      * @param interruptable Tells if the wait can be interrupted or not
166      * @return <code>true</code> if the Future is ready
167      * @throws InterruptedException If the thread has been interrupted
168      * when it's not allowed.
169      */
170     private boolean await0(long timeoutMillis, boolean interruptable) throws InterruptedException {
171         long endTime = System.currentTimeMillis() + timeoutMillis;
172 
173         synchronized (lock) {
174             if (ready) {
175                 return ready;
176             } else if (timeoutMillis <= 0) {
177                 return ready;
178             }
179 
180             waiters++;
181             try {
182                 for (;;) {
183                     try {
184                         long timeOut = Math.min(timeoutMillis, DEAD_LOCK_CHECK_INTERVAL);
185                         lock.wait(timeOut);
186                     } catch (InterruptedException e) {
187                         if (interruptable) {
188                             throw e;
189                         }
190                     }
191 
192                     if (ready) {
193                         return true;
194                     } else {
195                         if (endTime < System.currentTimeMillis()) {
196                             return ready;
197                         }
198                     }
199                 }
200             } finally {
201                 waiters--;
202                 if (!ready) {
203                     checkDeadLock();
204                 }
205             }
206         }
207     }
208 
209     
210     /**
211      * 
212      * TODO checkDeadLock.
213      *
214      */
215     private void checkDeadLock() {
216         // Only read / write / connect / write future can cause dead lock. 
217         if (!(this instanceof CloseFuture || this instanceof WriteFuture ||
218               this instanceof ReadFuture || this instanceof ConnectFuture)) {
219             return;
220         }
221         
222         // Get the current thread stackTrace. 
223         // Using Thread.currentThread().getStackTrace() is the best solution,
224         // even if slightly less efficient than doing a new Exception().getStackTrace(),
225         // as internally, it does exactly the same thing. The advantage of using
226         // this solution is that we may benefit some improvement with some
227         // future versions of Java.
228         StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
229 
230         // Simple and quick check.
231         for (StackTraceElement s: stackTrace) {
232             if (AbstractPollingIoProcessor.class.getName().equals(s.getClassName())) {
233                 IllegalStateException e = new IllegalStateException( "t" );
234                 e.getStackTrace();
235                 throw new IllegalStateException(
236                     "DEAD LOCK: " + IoFuture.class.getSimpleName() +
237                     ".await() was invoked from an I/O processor thread.  " +
238                     "Please use " + IoFutureListener.class.getSimpleName() +
239                     " or configure a proper thread model alternatively.");
240             }
241         }
242 
243         // And then more precisely.
244         for (StackTraceElement s: stackTrace) {
245             try {
246                 Class<?> cls = DefaultIoFuture.class.getClassLoader().loadClass(s.getClassName());
247                 if (IoProcessor.class.isAssignableFrom(cls)) {
248                     throw new IllegalStateException(
249                         "DEAD LOCK: " + IoFuture.class.getSimpleName() +
250                         ".await() was invoked from an I/O processor thread.  " +
251                         "Please use " + IoFutureListener.class.getSimpleName() +
252                         " or configure a proper thread model alternatively.");
253                 }
254             } catch (Exception cnfe) {
255                 // Ignore
256             }
257         }
258     }
259 
260     /**
261      * {@inheritDoc}
262      */
263     public boolean isDone() {
264         synchronized (lock) {
265             return ready;
266         }
267     }
268 
269     /**
270      * Sets the result of the asynchronous operation, and mark it as finished.
271      */
272     public void setValue(Object newValue) {
273         synchronized (lock) {
274             // Allow only once.
275             if (ready) {
276                 return;
277             }
278 
279             result = newValue;
280             ready = true;
281             if (waiters > 0) {
282                 lock.notifyAll();
283             }
284         }
285 
286         notifyListeners();
287     }
288 
289     /**
290      * Returns the result of the asynchronous operation.
291      */
292     protected Object getValue() {
293         synchronized (lock) {
294             return result;
295         }
296     }
297 
298     /**
299      * {@inheritDoc}
300      */
301     public IoFuture addListener(IoFutureListener<?> listener) {
302         if (listener == null) {
303             throw new NullPointerException("listener");
304         }
305 
306         boolean notifyNow = false;
307         synchronized (lock) {
308             if (ready) {
309                 notifyNow = true;
310             } else {
311                 if (firstListener == null) {
312                     firstListener = listener;
313                 } else {
314                     if (otherListeners == null) {
315                         otherListeners = new ArrayList<IoFutureListener<?>>(1);
316                     }
317                     otherListeners.add(listener);
318                 }
319             }
320         }
321 
322         if (notifyNow) {
323             notifyListener(listener);
324         }
325         return this;
326     }
327 
328     /**
329      * {@inheritDoc}
330      */
331     public IoFuture removeListener(IoFutureListener<?> listener) {
332         if (listener == null) {
333             throw new NullPointerException("listener");
334         }
335 
336         synchronized (lock) {
337             if (!ready) {
338                 if (listener == firstListener) {
339                     if (otherListeners != null && !otherListeners.isEmpty()) {
340                         firstListener = otherListeners.remove(0);
341                     } else {
342                         firstListener = null;
343                     }
344                 } else if (otherListeners != null) {
345                     otherListeners.remove(listener);
346                 }
347             }
348         }
349 
350         return this;
351     }
352 
353     private void notifyListeners() {
354         // There won't be any visibility problem or concurrent modification
355         // because 'ready' flag will be checked against both addListener and
356         // removeListener calls.
357         if (firstListener != null) {
358             notifyListener(firstListener);
359             firstListener = null;
360 
361             if (otherListeners != null) {
362                 for (IoFutureListener<?> l : otherListeners) {
363                     notifyListener(l);
364                 }
365                 otherListeners = null;
366             }
367         }
368     }
369 
370     @SuppressWarnings("unchecked")
371     private void notifyListener(IoFutureListener l) {
372         try {
373             l.operationComplete(this);
374         } catch (Throwable t) {
375             ExceptionMonitor.getInstance().exceptionCaught(t);
376         }
377     }
378 }