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