1   package org.mortbay.io.nio;
2   
3   import java.io.IOException;
4   import java.nio.channels.ClosedChannelException;
5   import java.nio.channels.SelectableChannel;
6   import java.nio.channels.SelectionKey;
7   import java.nio.channels.SocketChannel;
8   
9   import javax.net.ssl.SSLException;
10  
11  import org.mortbay.io.AsyncEndPoint;
12  import org.mortbay.io.Buffer;
13  import org.mortbay.io.Connection;
14  import org.mortbay.io.nio.SelectorManager.SelectSet;
15  import org.mortbay.jetty.EofException;
16  import org.mortbay.jetty.HttpException;
17  import org.mortbay.log.Log;
18  import org.mortbay.thread.Timeout;
19  
20  /* ------------------------------------------------------------ */
21  /**
22   * An Endpoint that can be scheduled by {@link SelectorManager}.
23   * 
24   * @author gregw
25   *
26   */
27  public class SelectChannelEndPoint extends ChannelEndPoint implements Runnable, AsyncEndPoint
28  {
29      protected SelectorManager _manager;
30      protected SelectorManager.SelectSet _selectSet;
31      protected boolean _dispatched = false;
32      protected boolean _redispatched = false;
33      protected boolean _writable = true; 
34      protected SelectionKey _key;
35      protected int _interestOps;
36      protected boolean _readBlocked;
37      protected boolean _writeBlocked;
38      protected Connection _connection;
39      private boolean _open;
40      private Timeout.Task _idleTask = new IdleTask();
41  
42      /* ------------------------------------------------------------ */
43      public Connection getConnection()
44      {
45          return _connection;
46      }
47      
48      /* ------------------------------------------------------------ */
49      public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key)
50      {
51          super(channel);
52  
53          _manager = selectSet.getManager();
54          _selectSet = selectSet;
55          _connection = _manager.newConnection(channel,this);
56          _dispatched = false;
57          _redispatched = false;
58          _open=true;
59          _manager.endPointOpened(this);
60          
61          _key = key;
62          scheduleIdle();
63      }
64  
65      /* ------------------------------------------------------------ */
66      /** Called by selectSet to schedule handling
67       * 
68       */
69      public void schedule() throws IOException
70      {
71          // If threads are blocked on this
72          synchronized (this)
73          {
74              // If there is no key, then do nothing
75              if (_key == null || !_key.isValid())
76              {
77                  _readBlocked=false;
78                  _writeBlocked=false;
79                  this.notifyAll();
80                  return;
81              }
82              
83              // If there are threads dispatched reading and writing
84              if (_readBlocked || _writeBlocked)
85              {
86                  // assert _dispatched;
87                  if (_readBlocked && _key.isReadable())
88                      _readBlocked=false;
89                  if (_writeBlocked && _key.isWritable())
90                      _writeBlocked=false;
91  
92                  // wake them up is as good as a dispatched.
93                  this.notifyAll();
94                  
95                  // we are not interested in further selecting
96                  _key.interestOps(0);
97                  return;
98              }
99              
100             // Otherwise if we are still dispatched
101             if (!isReadyForDispatch())
102             {
103                 // we are not interested in further selecting
104                 _key.interestOps(0);
105                 return;
106             }
107             
108             // Remove writeable op
109             if ((_key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && (_key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)
110             {
111                 // Remove writeable op
112                 _interestOps = _key.interestOps() & ~SelectionKey.OP_WRITE;
113                 _key.interestOps(_interestOps);
114                 _writable = true; // Once writable is in ops, only removed with dispatch.
115             }
116 
117             if (!dispatch())
118                 updateKey();
119         }
120     }
121         
122     /* ------------------------------------------------------------ */
123     public boolean dispatch() 
124     {
125         synchronized(this)
126         {
127             if (_dispatched)
128             {
129                 _redispatched=true;
130                 return true;
131             }
132 
133             _dispatched = _manager.dispatch((Runnable)this);   
134             if(!_dispatched)
135                 Log.warn("Dispatched Failed!");
136             return _dispatched;
137         }
138     }
139 
140     /* ------------------------------------------------------------ */
141     /**
142      * Called when a dispatched thread is no longer handling the endpoint. The selection key
143      * operations are updated.
144      */
145     protected boolean undispatch()
146     {
147         synchronized (this)
148         {
149             if (_redispatched)
150             {
151                 _redispatched=false;
152                 return false;
153             }
154             _dispatched = false;
155             updateKey();
156         }
157         return true;
158     }
159 
160     /* ------------------------------------------------------------ */
161     public void scheduleIdle()
162     {
163         _selectSet.scheduleIdle(_idleTask);
164     }
165 
166     /* ------------------------------------------------------------ */
167     public void cancelIdle()
168     {
169         _selectSet.cancelIdle(_idleTask);
170     }
171 
172     /* ------------------------------------------------------------ */
173     protected void idleExpired()
174     {
175         try
176         {
177             close();
178         }
179         catch (IOException e)
180         {
181             Log.ignore(e);
182         }
183     }
184 
185     /* ------------------------------------------------------------ */
186     /*
187      */
188     public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
189     {
190         int l = super.flush(header, buffer, trailer);
191         _writable = l > 0;
192         return l;
193     }
194 
195     /* ------------------------------------------------------------ */
196     /*
197      */
198     public int flush(Buffer buffer) throws IOException
199     {
200         int l = super.flush(buffer);
201         _writable = l > 0;
202         return l;
203     }
204 
205     /* ------------------------------------------------------------ */
206     public boolean isReadyForDispatch()
207     {
208         return !_dispatched;
209     }
210     
211     /* ------------------------------------------------------------ */
212     /*
213      * Allows thread to block waiting for further events.
214      */
215     public boolean blockReadable(long timeoutMs) throws IOException
216     {
217         synchronized (this)
218         {
219             long start=_selectSet.getNow();
220             try
221             {   
222                 _readBlocked=true;
223                 while (isOpen() && _readBlocked)
224                 {
225                     try
226                     {
227                         updateKey();
228                         this.wait(timeoutMs);
229 
230                         if (_readBlocked && timeoutMs<(_selectSet.getNow()-start))
231                             return false;
232                     }
233                     catch (InterruptedException e)
234                     {
235                         Log.warn(e);
236                     }
237                 }
238             }
239             finally
240             {
241                 _readBlocked=false;
242             }
243         }
244         return true;
245     }
246 
247     /* ------------------------------------------------------------ */
248     /*
249      * Allows thread to block waiting for further events.
250      */
251     public boolean blockWritable(long timeoutMs) throws IOException
252     {
253         synchronized (this)
254         {
255             long start=_selectSet.getNow();
256             try
257             {   
258                 _writeBlocked=true;
259                 while (isOpen() && _writeBlocked)
260                 {
261                     try
262                     {
263                         updateKey();
264                         this.wait(timeoutMs);
265 
266                         if (_writeBlocked && timeoutMs<(_selectSet.getNow()-start))
267                             return false;
268                     }
269                     catch (InterruptedException e)
270                     {
271                         Log.warn(e);
272                     }
273                 }
274             }
275             finally
276             {
277                 _writeBlocked=false;
278             }
279         }
280         return true;
281     }
282 
283     /* ------------------------------------------------------------ */
284     public void setWritable(boolean writable)
285     {
286         _writable=writable;
287     }
288     
289     /* ------------------------------------------------------------ */
290     public void scheduleWrite()
291     {
292         _writable=false;
293         updateKey();
294     }
295     
296     /* ------------------------------------------------------------ */
297     /**
298      * Updates selection key. Adds operations types to the selection key as needed. No operations
299      * are removed as this is only done during dispatch. This method records the new key and
300      * schedules a call to doUpdateKey to do the keyChange
301      */
302     private void updateKey()
303     {
304         synchronized (this)
305         {
306         
307             int ops=-1;
308             if (getChannel().isOpen())
309             {
310                 _interestOps = 
311                     ((!_dispatched || _readBlocked)  ? SelectionKey.OP_READ  : 0) 
312                 |   ((!_writable   || _writeBlocked) ? SelectionKey.OP_WRITE : 0);
313                 try
314                 {
315                     ops = ((_key!=null && _key.isValid())?_key.interestOps():-1);
316                 }
317                 catch(Exception e)
318                 {
319                     _key=null;
320                     Log.ignore(e);
321                 }
322             }
323             if(_interestOps == ops && getChannel().isOpen())
324                 return;
325             
326         }
327         _selectSet.addChange(this);
328         _selectSet.wakeup();
329     }
330     
331     /* ------------------------------------------------------------ */
332     /**
333      * Synchronize the interestOps with the actual key. Call is scheduled by a call to updateKey
334      */
335     void doUpdateKey()
336     {
337         synchronized (this)
338         {
339             if (getChannel().isOpen())
340             {
341                 if (_interestOps>0)
342                 {
343                     if (_key==null || !_key.isValid())
344                     {
345                         SelectableChannel sc = (SelectableChannel)getChannel();
346                         if (sc.isRegistered())
347                         {
348                             updateKey();   
349                         }
350                         else
351                         {
352                             try
353                             {
354                                 _key=((SelectableChannel)getChannel()).register(_selectSet.getSelector(),_interestOps,this);
355                             }
356                             catch (Exception e)
357                             {
358                                 Log.ignore(e);
359                                 if (_key!=null && _key.isValid())
360                                 {
361                                     _key.cancel();
362                                 }
363                                 cancelIdle();
364                                 if (_open)
365                                     _manager.endPointClosed(this);
366                                 _open=false;
367                                 _key = null;
368                             }
369                         }
370                     }
371                     else
372                     {
373                         _key.interestOps(_interestOps);
374                     }
375                 }
376                 else
377                 {
378                     if (_key.isValid())
379                         _key.interestOps(0);
380                     else
381                         _key=null;
382                 }
383             }
384             else    
385             {
386                 if (_key!=null && _key.isValid())
387                     _key.cancel(); 
388                 
389                 cancelIdle();
390                 if (_open)
391                     _manager.endPointClosed(this);
392                 _open=false;
393                 _key = null;
394             }
395         }
396     }
397 
398     /* ------------------------------------------------------------ */
399     /* 
400      */
401     public void run()
402     {
403         
404         boolean dispatched=true;
405         do
406         {
407             try
408             {
409                 _connection.handle();
410             }
411             catch (ClosedChannelException e)
412             {
413                 Log.ignore(e);
414             }
415             catch (EofException e)
416             {
417                 Log.debug("EOF", e);
418                 try{close();}
419                 catch(IOException e2){Log.ignore(e2);}
420             }
421             catch (HttpException e)
422             {
423                 Log.debug("BAD", e);
424                 try{close();}
425                 catch(IOException e2){Log.ignore(e2);}
426             }
427             catch (Throwable e)
428             {
429                 Log.warn("handle failed", e);
430                 try{close();}
431                 catch(IOException e2){Log.ignore(e2);}
432             }
433             finally
434             {
435                 dispatched=!undispatch();
436             }   
437         }
438         while(dispatched);
439     }
440 
441     /* ------------------------------------------------------------ */
442     /*
443      * @see org.mortbay.io.nio.ChannelEndPoint#close()
444      */
445     public void close() throws IOException
446     {
447         try
448         {
449             super.close();
450         }
451         catch (IOException e)
452         {
453             Log.ignore(e);
454         }   
455         finally
456         {
457             updateKey();
458         }
459     }
460     
461     /* ------------------------------------------------------------ */
462     public String toString()
463     {
464         return "SCEP@" + hashCode() + "[d=" + _dispatched + ",io=" + _interestOps + ",w=" + _writable + ",b=" + _readBlocked + "|" + _writeBlocked + "]";
465     }
466 
467     /* ------------------------------------------------------------ */
468     public Timeout.Task getTimeoutTask()
469     {
470         return _idleTask;
471     }
472 
473     /* ------------------------------------------------------------ */
474     public SelectSet getSelectSet()
475     {
476         return _selectSet;
477     }
478 
479     /* ------------------------------------------------------------ */
480     /* ------------------------------------------------------------ */
481     /* ------------------------------------------------------------ */
482     public class IdleTask extends Timeout.Task 
483     {
484         /* ------------------------------------------------------------ */
485         /*
486          * @see org.mortbay.thread.Timeout.Task#expire()
487          */
488         public void expired()
489         {
490             idleExpired();
491         }
492 
493         public String toString()
494         {
495             return "TimeoutTask:" + SelectChannelEndPoint.this.toString();
496         }
497 
498     }
499 
500 }