1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.transport.socket.nio;
21
22 import java.io.IOException;
23 import java.nio.channels.SelectionKey;
24 import java.nio.channels.Selector;
25 import java.nio.channels.SocketChannel;
26 import java.util.Queue;
27 import java.util.Set;
28 import java.util.concurrent.ConcurrentLinkedQueue;
29 import java.util.concurrent.Executor;
30
31 import org.apache.mina.common.ByteBuffer;
32 import org.apache.mina.common.ExceptionMonitor;
33 import org.apache.mina.common.IdleStatus;
34 import org.apache.mina.common.IoFilter.WriteRequest;
35 import org.apache.mina.common.WriteTimeoutException;
36 import org.apache.mina.util.NamePreservingRunnable;
37
38
39
40
41
42
43
44 class SocketIoProcessor {
45 private final Object lock = new Object();
46
47 private final String threadName;
48
49 private final Executor executor;
50
51 private volatile Selector selector;
52
53 private final Queue<SocketSessionImpl> newSessions = new ConcurrentLinkedQueue<SocketSessionImpl>();
54
55 private final Queue<SocketSessionImpl> removingSessions = new ConcurrentLinkedQueue<SocketSessionImpl>();
56
57 private final Queue<SocketSessionImpl> flushingSessions = new ConcurrentLinkedQueue<SocketSessionImpl>();
58
59 private final Queue<SocketSessionImpl> trafficControllingSessions = new ConcurrentLinkedQueue<SocketSessionImpl>();
60
61 private Worker worker;
62
63 private long lastIdleCheckTime = System.currentTimeMillis();
64
65 SocketIoProcessor(String threadName, Executor executor) {
66 this.threadName = threadName;
67 this.executor = executor;
68 }
69
70 void addNew(SocketSessionImpl session) throws IOException {
71 newSessions.add(session);
72 startupWorker();
73 }
74
75 void remove(SocketSessionImpl session) throws IOException {
76 scheduleRemove(session);
77 startupWorker();
78 }
79
80 private void startupWorker() throws IOException {
81 synchronized (lock) {
82 if (worker == null) {
83 selector = Selector.open();
84 worker = new Worker();
85 executor.execute(new NamePreservingRunnable(worker));
86 }
87 selector.wakeup();
88 }
89 }
90
91 void flush(SocketSessionImpl session) {
92 if ( scheduleFlush(session) ) {
93 Selector selector = this.selector;
94 if (selector != null) {
95 selector.wakeup();
96 }
97 }
98 }
99
100 void updateTrafficMask(SocketSessionImpl session) {
101 scheduleTrafficControl(session);
102 Selector selector = this.selector;
103 if (selector != null) {
104 selector.wakeup();
105 }
106 }
107
108 private void scheduleRemove(SocketSessionImpl session) {
109 removingSessions.add(session);
110 }
111
112 private boolean scheduleFlush(SocketSessionImpl session) {
113 if (session.setScheduledForFlush(true)) {
114 flushingSessions.add(session);
115
116 return true;
117 }
118
119 return false;
120 }
121
122 private void scheduleTrafficControl(SocketSessionImpl session) {
123 trafficControllingSessions.add(session);
124 }
125
126 private void doAddNew() {
127 Selector selector = this.selector;
128 for (;;) {
129 SocketSessionImpl session = newSessions.poll();
130
131 if (session == null)
132 break;
133
134 SocketChannel ch = session.getChannel();
135 try {
136 ch.configureBlocking(false);
137 session.setSelectionKey(ch.register(selector,
138 SelectionKey.OP_READ, session));
139
140
141
142 session.getServiceListeners().fireSessionCreated(session);
143 } catch (IOException e) {
144
145
146 session.getFilterChain().fireExceptionCaught(session, e);
147 }
148 }
149 }
150
151 private void doRemove() {
152 for (;;) {
153 SocketSessionImpl session = removingSessions.poll();
154
155 if (session == null)
156 break;
157
158 SocketChannel ch = session.getChannel();
159 SelectionKey key = session.getSelectionKey();
160
161
162 if (key == null) {
163 scheduleRemove(session);
164 break;
165 }
166
167 if (!key.isValid()) {
168 continue;
169 }
170
171 try {
172 key.cancel();
173 ch.close();
174 } catch (IOException e) {
175 session.getFilterChain().fireExceptionCaught(session, e);
176 } finally {
177 releaseWriteBuffers(session);
178 session.getServiceListeners().fireSessionDestroyed(session);
179 }
180 }
181 }
182
183 private void process(Set<SelectionKey> selectedKeys) {
184 for (SelectionKey key : selectedKeys) {
185 SocketSessionImpl session = (SocketSessionImpl) key.attachment();
186
187 if (key.isReadable() && session.getTrafficMask().isReadable()) {
188 read(session);
189 }
190
191 if (key.isWritable() && session.getTrafficMask().isWritable()) {
192 scheduleFlush(session);
193 }
194 }
195
196 selectedKeys.clear();
197 }
198
199 private void read(SocketSessionImpl session) {
200 ByteBuffer buf = ByteBuffer.allocate(session.getReadBufferSize());
201 SocketChannel ch = session.getChannel();
202
203 try {
204 int readBytes = 0;
205 int ret;
206
207 try {
208 while ((ret = ch.read(buf.buf())) > 0) {
209 readBytes += ret;
210 }
211 } finally {
212 buf.flip();
213 }
214
215 session.increaseReadBytes(readBytes);
216
217 if (readBytes > 0) {
218 session.getFilterChain().fireMessageReceived(session, buf);
219 buf = null;
220
221 if (readBytes * 2 < session.getReadBufferSize()) {
222 if (session.getReadBufferSize() > 64) {
223 session.setReadBufferSize(session.getReadBufferSize() >>> 1);
224 }
225 } else if (readBytes == session.getReadBufferSize()) {
226 int newReadBufferSize = session.getReadBufferSize() << 1;
227 if (newReadBufferSize <= (((SocketSessionConfig) session.getConfig()).getReceiveBufferSize() << 1)) {
228
229
230
231 session.setReadBufferSize(newReadBufferSize);
232 }
233 }
234 }
235 if (ret < 0) {
236 scheduleRemove(session);
237 }
238 } catch (Throwable e) {
239 if (e instanceof IOException)
240 scheduleRemove(session);
241 session.getFilterChain().fireExceptionCaught(session, e);
242 } finally {
243 if (buf != null)
244 buf.release();
245 }
246 }
247
248 private void notifyIdleness() {
249
250 long currentTime = System.currentTimeMillis();
251 if ((currentTime - lastIdleCheckTime) >= 1000) {
252 lastIdleCheckTime = currentTime;
253 Set<SelectionKey> keys = selector.keys();
254 if (keys != null) {
255 for (SelectionKey key : keys) {
256 SocketSessionImpl session = (SocketSessionImpl) key
257 .attachment();
258 notifyIdleness(session, currentTime);
259 }
260 }
261 }
262 }
263
264 private void notifyIdleness(SocketSessionImpl session, long currentTime) {
265 notifyIdleness0(session, currentTime, session
266 .getIdleTimeInMillis(IdleStatus.BOTH_IDLE),
267 IdleStatus.BOTH_IDLE, Math.max(session.getLastIoTime(), session
268 .getLastIdleTime(IdleStatus.BOTH_IDLE)));
269 notifyIdleness0(session, currentTime, session
270 .getIdleTimeInMillis(IdleStatus.READER_IDLE),
271 IdleStatus.READER_IDLE, Math.max(session.getLastReadTime(),
272 session.getLastIdleTime(IdleStatus.READER_IDLE)));
273 notifyIdleness0(session, currentTime, session
274 .getIdleTimeInMillis(IdleStatus.WRITER_IDLE),
275 IdleStatus.WRITER_IDLE, Math.max(session.getLastWriteTime(),
276 session.getLastIdleTime(IdleStatus.WRITER_IDLE)));
277
278 notifyWriteTimeout(session, currentTime, session
279 .getWriteTimeoutInMillis(), session.getLastWriteTime());
280 }
281
282 private void notifyIdleness0(SocketSessionImpl session, long currentTime,
283 long idleTime, IdleStatus status, long lastIoTime) {
284 if (idleTime > 0 && lastIoTime != 0
285 && (currentTime - lastIoTime) >= idleTime) {
286 session.increaseIdleCount(status);
287 session.getFilterChain().fireSessionIdle(session, status);
288 }
289 }
290
291 private void notifyWriteTimeout(SocketSessionImpl session,
292 long currentTime, long writeTimeout, long lastIoTime) {
293 SelectionKey key = session.getSelectionKey();
294 if (writeTimeout > 0 && (currentTime - lastIoTime) >= writeTimeout
295 && key != null && key.isValid()
296 && (key.interestOps() & SelectionKey.OP_WRITE) != 0) {
297 session.getFilterChain().fireExceptionCaught(session,
298 new WriteTimeoutException());
299 }
300 }
301
302 private void doFlush() {
303 for (;;) {
304 SocketSessionImpl session = flushingSessions.poll();
305
306 if (session == null)
307 break;
308
309 session.setScheduledForFlush(false);
310
311 if (!session.isConnected()) {
312 releaseWriteBuffers(session);
313 continue;
314 }
315
316 SelectionKey key = session.getSelectionKey();
317
318
319 if (key == null) {
320 scheduleFlush(session);
321 break;
322 }
323
324
325 if (!key.isValid()) {
326 continue;
327 }
328
329 try {
330 boolean flushedAll = doFlush(session);
331 if( flushedAll && !session.getWriteRequestQueue().isEmpty() && !session.isScheduledForFlush()) {
332 scheduleFlush( session );
333 }
334 } catch (IOException e) {
335 scheduleRemove(session);
336 session.getFilterChain().fireExceptionCaught(session, e);
337 }
338 }
339 }
340
341 private void releaseWriteBuffers(SocketSessionImpl session) {
342 Queue<WriteRequest> writeRequestQueue = session.getWriteRequestQueue();
343 WriteRequest req;
344
345 if ((req = writeRequestQueue.poll()) != null) {
346 ByteBuffer buf = (ByteBuffer) req.getMessage();
347 try {
348 buf.release();
349 } catch (IllegalStateException e) {
350 session.getFilterChain().fireExceptionCaught(session, e);
351 } finally {
352
353
354 if (buf.hasRemaining()) {
355 req.getFuture().setWritten(false);
356 } else {
357 session.getFilterChain().fireMessageSent(session, req);
358 }
359 }
360
361
362 while ((req = writeRequestQueue.poll()) != null) {
363 try {
364 ((ByteBuffer) req.getMessage()).release();
365 } catch (IllegalStateException e) {
366 session.getFilterChain().fireExceptionCaught(session, e);
367 } finally {
368 req.getFuture().setWritten(false);
369 }
370 }
371 }
372 }
373
374 private boolean doFlush(SocketSessionImpl session) throws IOException {
375
376 SelectionKey key = session.getSelectionKey();
377 key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
378
379 SocketChannel ch = session.getChannel();
380 Queue<WriteRequest> writeRequestQueue = session.getWriteRequestQueue();
381
382 int writtenBytes = 0;
383 int maxWrittenBytes = ((SocketSessionConfig) session.getConfig()).getSendBufferSize() << 1;
384 try {
385 for (;;) {
386 WriteRequest req = writeRequestQueue.peek();
387
388 if (req == null)
389 break;
390
391 ByteBuffer buf = (ByteBuffer) req.getMessage();
392 if (buf.remaining() == 0) {
393 writeRequestQueue.poll();
394
395 session.increaseWrittenMessages();
396
397 buf.reset();
398 session.getFilterChain().fireMessageSent(session, req);
399 continue;
400 }
401
402 if (key.isWritable()) {
403 writtenBytes += ch.write(buf.buf());
404 }
405
406 if (buf.hasRemaining() || writtenBytes >= maxWrittenBytes) {
407
408 key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
409 return false;
410 }
411 }
412 } finally {
413 session.increaseWrittenBytes(writtenBytes);
414 }
415
416 return true;
417 }
418
419 private void doUpdateTrafficMask() {
420 if (trafficControllingSessions.isEmpty())
421 return;
422
423 for (;;) {
424 SocketSessionImpl session = trafficControllingSessions.poll();
425
426 if (session == null)
427 break;
428
429 SelectionKey key = session.getSelectionKey();
430
431
432
433 if (key == null) {
434 scheduleTrafficControl(session);
435 break;
436 }
437
438 if (!key.isValid()) {
439 continue;
440 }
441
442
443
444 int ops = SelectionKey.OP_READ;
445 Queue<WriteRequest> writeRequestQueue = session
446 .getWriteRequestQueue();
447 synchronized (writeRequestQueue) {
448 if (!writeRequestQueue.isEmpty()) {
449 ops |= SelectionKey.OP_WRITE;
450 }
451 }
452
453
454 int mask = session.getTrafficMask().getInterestOps();
455 key.interestOps(ops & mask);
456 }
457 }
458
459 private class Worker implements Runnable {
460 public void run() {
461 Thread.currentThread().setName(SocketIoProcessor.this.threadName);
462
463 Selector selector = SocketIoProcessor.this.selector;
464 for (;;) {
465 try {
466 int nKeys = selector.select(1000);
467 doAddNew();
468 doUpdateTrafficMask();
469
470 if (nKeys > 0) {
471 process(selector.selectedKeys());
472 }
473
474 doFlush();
475 doRemove();
476 notifyIdleness();
477
478 if (selector.keys().isEmpty()) {
479 synchronized (lock) {
480 if (selector.keys().isEmpty()
481 && newSessions.isEmpty()) {
482 worker = null;
483
484 try {
485 selector.close();
486 } catch (IOException e) {
487 ExceptionMonitor.getInstance()
488 .exceptionCaught(e);
489 } finally {
490 selector = null;
491 }
492
493 break;
494 }
495 }
496 }
497 } catch (Throwable t) {
498 ExceptionMonitor.getInstance().exceptionCaught(t);
499
500 try {
501 Thread.sleep(1000);
502 } catch (InterruptedException e1) {
503 ExceptionMonitor.getInstance().exceptionCaught(e1);
504 }
505 }
506 }
507 }
508 }
509 }