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