1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.core.polling;
21
22 import java.io.IOException;
23 import java.net.PortUnreachableException;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Queue;
30 import java.util.concurrent.ConcurrentLinkedQueue;
31 import java.util.concurrent.Executor;
32 import java.util.concurrent.atomic.AtomicInteger;
33
34 import org.apache.mina.core.buffer.IoBuffer;
35 import org.apache.mina.core.file.FileRegion;
36 import org.apache.mina.core.filterchain.IoFilterChain;
37 import org.apache.mina.core.future.DefaultIoFuture;
38 import org.apache.mina.core.service.AbstractIoService;
39 import org.apache.mina.core.service.IoProcessor;
40 import org.apache.mina.core.session.AbstractIoSession;
41 import org.apache.mina.core.session.IoSession;
42 import org.apache.mina.core.session.IoSessionConfig;
43 import org.apache.mina.core.session.SessionState;
44 import org.apache.mina.core.write.WriteRequest;
45 import org.apache.mina.core.write.WriteRequestQueue;
46 import org.apache.mina.core.write.WriteToClosedSessionException;
47 import org.apache.mina.transport.socket.AbstractDatagramSessionConfig;
48 import org.apache.mina.util.ExceptionMonitor;
49 import org.apache.mina.util.NamePreservingRunnable;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53
54
55
56
57
58
59
60
61 public abstract class AbstractPollingIoProcessor<T extends AbstractIoSession>
62 implements IoProcessor<T> {
63
64 private final static Logger LOG = LoggerFactory.getLogger(IoProcessor.class);
65
66
67
68
69
70
71
72 private static final int WRITE_SPIN_COUNT = 256;
73
74
75 private static final long SELECT_TIMEOUT = 1000L;
76
77
78 private static final Map<Class<?>, AtomicInteger> threadIds = new HashMap<Class<?>, AtomicInteger>();
79
80 private final Object lock = new Object();
81
82 private final String threadName;
83
84 private final Executor executor;
85
86
87 private final Queue<T> newSessions = new ConcurrentLinkedQueue<T>();
88
89
90 private final Queue<T> removingSessions = new ConcurrentLinkedQueue<T>();
91
92
93 private final Queue<T> flushingSessions = new ConcurrentLinkedQueue<T>();
94
95
96 private final Queue<T> trafficControllingSessions = new ConcurrentLinkedQueue<T>();
97
98
99 private Processor processor;
100
101 private long lastIdleCheckTime;
102
103 private final Object disposalLock = new Object();
104
105 private volatile boolean disposing;
106
107 private volatile boolean disposed;
108
109 private final DefaultIoFuture disposalFuture = new DefaultIoFuture(null);
110
111
112
113
114
115
116
117 protected AbstractPollingIoProcessor(Executor executor) {
118 if (executor == null) {
119 throw new NullPointerException("executor");
120 }
121
122 this.threadName = nextThreadName();
123 this.executor = executor;
124 }
125
126
127
128
129
130
131
132
133
134 private String nextThreadName() {
135 Class<?> cls = getClass();
136 int newThreadId;
137
138
139
140
141 synchronized (threadIds) {
142
143 AtomicInteger threadId = threadIds.get(cls);
144
145 if (threadId == null) {
146
147
148
149 newThreadId = 1;
150 threadIds.put(cls, new AtomicInteger(newThreadId));
151 } else {
152
153 newThreadId = threadId.incrementAndGet();
154 }
155 }
156
157
158 return cls.getSimpleName() + '-' + newThreadId;
159 }
160
161
162
163
164 public final boolean isDisposing() {
165 return disposing;
166 }
167
168
169
170
171 public final boolean isDisposed() {
172 return disposed;
173 }
174
175
176
177
178 public final void dispose() {
179 if (disposed) {
180 return;
181 }
182
183 synchronized (disposalLock) {
184 if (!disposing) {
185 disposing = true;
186 startupProcessor();
187 }
188 }
189
190 disposalFuture.awaitUninterruptibly();
191 disposed = true;
192 }
193
194
195
196
197
198
199 protected abstract void dispose0() throws Exception;
200
201
202
203
204
205
206
207 protected abstract int select(long timeout) throws Exception;
208
209
210
211
212
213
214 protected abstract int select() throws Exception;
215
216
217
218
219
220
221 protected abstract boolean isSelectorEmpty();
222
223
224
225
226 protected abstract void wakeup();
227
228
229
230
231
232
233 protected abstract Iterator<T> allSessions();
234
235
236
237
238
239
240 protected abstract Iterator<T> selectedSessions();
241
242
243
244
245
246
247 protected abstract SessionState getState(T session);
248
249
250
251
252
253
254 protected abstract boolean isWritable(T session);
255
256
257
258
259
260
261 protected abstract boolean isReadable(T session);
262
263
264
265
266
267
268 protected abstract void setInterestedInWrite(T session, boolean isInterested)
269 throws Exception;
270
271
272
273
274
275
276 protected abstract void setInterestedInRead(T session, boolean isInterested)
277 throws Exception;
278
279
280
281
282
283
284 protected abstract boolean isInterestedInRead(T session);
285
286
287
288
289
290
291 protected abstract boolean isInterestedInWrite(T session);
292
293
294
295
296
297
298 protected abstract void init(T session) throws Exception;
299
300
301
302
303
304
305 protected abstract void destroy(T session) throws Exception;
306
307
308
309
310
311
312
313
314
315 protected abstract int read(T session, IoBuffer buf) throws Exception;
316
317
318
319
320
321
322
323
324
325
326
327 protected abstract int write(T session, IoBuffer buf, int length)
328 throws Exception;
329
330
331
332
333
334
335
336
337
338
339
340 protected abstract int transferFile(T session, FileRegion region, int length)
341 throws Exception;
342
343
344
345
346 public final void add(T session) {
347 if (isDisposing()) {
348 throw new IllegalStateException("Already disposed.");
349 }
350
351
352 newSessions.add(session);
353 startupProcessor();
354 }
355
356
357
358
359 public final void remove(T session) {
360 scheduleRemove(session);
361 startupProcessor();
362 }
363
364 private void scheduleRemove(T session) {
365 removingSessions.add(session);
366 }
367
368
369
370
371 public final void flush(T session) {
372 boolean needsWakeup = flushingSessions.isEmpty();
373 if (scheduleFlush(session) && needsWakeup) {
374 wakeup();
375 }
376 }
377
378 private boolean scheduleFlush(T session) {
379 if (session.setScheduledForFlush(true)) {
380
381 flushingSessions.add(session);
382 return true;
383 }
384 return false;
385 }
386
387
388
389
390 public final void updateTrafficMask(T session) {
391 scheduleTrafficControl(session);
392 wakeup();
393 }
394
395 private void scheduleTrafficControl(T session) {
396 trafficControllingSessions.add(session);
397 }
398
399
400
401
402
403 private void startupProcessor() {
404 synchronized (lock) {
405 if (processor == null) {
406 processor = new Processor();
407 executor.execute(new NamePreservingRunnable(processor,
408 threadName));
409 }
410 }
411
412
413
414 wakeup();
415 }
416
417
418
419
420
421
422
423 private int handleNewSessions() {
424 int addedSessions = 0;
425
426 for (;;) {
427 T session = newSessions.poll();
428
429 if (session == null) {
430
431 break;
432 }
433
434 if (addNow(session)) {
435
436 addedSessions++;
437 }
438 }
439
440 return addedSessions;
441 }
442
443 private boolean addNow(T session) {
444 boolean registered = false;
445 boolean notified = false;
446
447 try {
448 init(session);
449 registered = true;
450
451
452 session.getService().getFilterChainBuilder().buildFilterChain(
453 session.getFilterChain());
454
455
456
457 ((AbstractIoService) session.getService()).getListeners()
458 .fireSessionCreated(session);
459 notified = true;
460 } catch (Throwable e) {
461 if (notified) {
462
463
464 scheduleRemove(session);
465 IoFilterChain filterChain = session.getFilterChain();
466 filterChain.fireExceptionCaught(e);
467 wakeup();
468 } else {
469 ExceptionMonitor.getInstance().exceptionCaught(e);
470 try {
471 destroy(session);
472 } catch (Exception e1) {
473 ExceptionMonitor.getInstance().exceptionCaught(e1);
474 } finally {
475 registered = false;
476 }
477 }
478 }
479 return registered;
480 }
481
482 private int removeSessions() {
483 int removedSessions = 0;
484
485 for (;;) {
486 T session = removingSessions.poll();
487
488 if (session == null) {
489
490 return removedSessions;
491 }
492
493 SessionState state = getState(session);
494
495 switch (state) {
496 case OPENED:
497 if (removeNow(session)) {
498 removedSessions++;
499 }
500
501 break;
502
503 case CLOSING:
504
505 break;
506
507 case OPENING:
508
509
510 newSessions.remove(session);
511
512 if (removeNow(session)) {
513 removedSessions++;
514 }
515
516 break;
517
518 default:
519 throw new IllegalStateException(String.valueOf(state));
520 }
521 }
522 }
523
524 private boolean removeNow(T session) {
525 clearWriteRequestQueue(session);
526
527 try {
528 destroy(session);
529 return true;
530 } catch (Exception e) {
531 IoFilterChain filterChain = session.getFilterChain();
532 filterChain.fireExceptionCaught(e);
533 } finally {
534 clearWriteRequestQueue(session);
535 ((AbstractIoService) session.getService()).getListeners()
536 .fireSessionDestroyed(session);
537 }
538 return false;
539 }
540
541 private void clearWriteRequestQueue(T session) {
542 WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
543 WriteRequest req;
544
545 List<WriteRequest> failedRequests = new ArrayList<WriteRequest>();
546
547 if ((req = writeRequestQueue.poll(session)) != null) {
548 Object m = req.getMessage();
549 if (m instanceof IoBuffer) {
550 IoBuffer buf = (IoBuffer) req.getMessage();
551
552
553
554 if (buf.hasRemaining()) {
555 buf.reset();
556 failedRequests.add(req);
557 } else {
558 IoFilterChain filterChain = session.getFilterChain();
559 filterChain.fireMessageSent(req);
560 }
561 } else {
562 failedRequests.add(req);
563 }
564
565
566 while ((req = writeRequestQueue.poll(session)) != null) {
567 failedRequests.add(req);
568 }
569 }
570
571
572 if (!failedRequests.isEmpty()) {
573 WriteToClosedSessionException cause = new WriteToClosedSessionException(
574 failedRequests);
575 for (WriteRequest r : failedRequests) {
576 session.decreaseScheduledBytesAndMessages(r);
577 r.getFuture().setException(cause);
578 }
579 IoFilterChain filterChain = session.getFilterChain();
580 filterChain.fireExceptionCaught(cause);
581 }
582 }
583
584 private void process() throws Exception {
585 for (Iterator<T> i = selectedSessions(); i.hasNext();) {
586 T session = i.next();
587 process(session);
588 i.remove();
589 }
590 }
591
592
593
594
595 private void process(T session) {
596
597 if (isReadable(session) && !session.isReadSuspended()) {
598 read(session);
599 }
600
601
602 if (isWritable(session) && !session.isWriteSuspended()) {
603 scheduleFlush(session);
604 }
605 }
606
607 private void read(T session) {
608 IoSessionConfig config = session.getConfig();
609 IoBuffer buf = IoBuffer.allocate(config.getReadBufferSize());
610
611 final boolean hasFragmentation = session.getTransportMetadata()
612 .hasFragmentation();
613
614 try {
615 int readBytes = 0;
616 int ret;
617
618 try {
619 if (hasFragmentation) {
620 while ((ret = read(session, buf)) > 0) {
621 readBytes += ret;
622 if (!buf.hasRemaining()) {
623 break;
624 }
625 }
626 } else {
627 ret = read(session, buf);
628 if (ret > 0) {
629 readBytes = ret;
630 }
631 }
632 } finally {
633 buf.flip();
634 }
635
636 if (readBytes > 0) {
637 IoFilterChain filterChain = session.getFilterChain();
638 filterChain.fireMessageReceived(buf);
639 buf = null;
640
641 if (hasFragmentation) {
642 if (readBytes << 1 < config.getReadBufferSize()) {
643 session.decreaseReadBufferSize();
644 } else if (readBytes == config.getReadBufferSize()) {
645 session.increaseReadBufferSize();
646 }
647 }
648 }
649
650 if (ret < 0) {
651 scheduleRemove(session);
652 }
653 } catch (Throwable e) {
654 if (e instanceof IOException) {
655 if (!(e instanceof PortUnreachableException)
656 || !AbstractDatagramSessionConfig.class
657 .isAssignableFrom(config.getClass())
658 || ((AbstractDatagramSessionConfig) config)
659 .isCloseOnPortUnreachable())
660
661 scheduleRemove(session);
662 }
663
664 IoFilterChain filterChain = session.getFilterChain();
665 filterChain.fireExceptionCaught(e);
666 }
667 }
668
669 private void notifyIdleSessions(long currentTime) throws Exception {
670
671 if (currentTime - lastIdleCheckTime >= SELECT_TIMEOUT) {
672 lastIdleCheckTime = currentTime;
673 AbstractIoSession.notifyIdleness(allSessions(), currentTime);
674 }
675 }
676
677 private void flush(long currentTime) {
678 final T firstSession = flushingSessions.peek();
679 if (firstSession == null) {
680 return;
681 }
682
683 T session = flushingSessions.poll();
684
685 for (;;) {
686 session.setScheduledForFlush(false);
687 SessionState state = getState(session);
688
689 switch (state) {
690 case OPENED:
691 try {
692 boolean flushedAll = flushNow(session, currentTime);
693 if (flushedAll
694 && !session.getWriteRequestQueue().isEmpty(session)
695 && !session.isScheduledForFlush()) {
696 scheduleFlush(session);
697 }
698 } catch (Exception e) {
699 scheduleRemove(session);
700 IoFilterChain filterChain = session.getFilterChain();
701 filterChain.fireExceptionCaught(e);
702 }
703
704 break;
705
706 case CLOSING:
707
708 break;
709
710 case OPENING:
711
712
713 scheduleFlush(session);
714 return;
715
716 default:
717 throw new IllegalStateException(String.valueOf(state));
718 }
719
720 session = flushingSessions.peek();
721 if (session == null || session == firstSession) {
722 break;
723 }
724 session = flushingSessions.poll();
725 }
726 }
727
728 private boolean flushNow(T session, long currentTime) {
729 if (!session.isConnected()) {
730 scheduleRemove(session);
731 return false;
732 }
733
734 final boolean hasFragmentation = session.getTransportMetadata()
735 .hasFragmentation();
736
737 final WriteRequestQueue writeRequestQueue = session
738 .getWriteRequestQueue();
739
740
741
742
743 final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize()
744 + (session.getConfig().getMaxReadBufferSize() >>> 1);
745 int writtenBytes = 0;
746 WriteRequest req = null;
747 try {
748
749 setInterestedInWrite(session, false);
750 do {
751
752 req = session.getCurrentWriteRequest();
753 if (req == null) {
754 req = writeRequestQueue.poll(session);
755 if (req == null) {
756 break;
757 }
758 session.setCurrentWriteRequest(req);
759 }
760
761 int localWrittenBytes = 0;
762 Object message = req.getMessage();
763 if (message instanceof IoBuffer) {
764 localWrittenBytes = writeBuffer(session, req,
765 hasFragmentation, maxWrittenBytes - writtenBytes,
766 currentTime);
767 if (localWrittenBytes > 0
768 && ((IoBuffer) message).hasRemaining()) {
769
770 writtenBytes += localWrittenBytes;
771 setInterestedInWrite(session, true);
772 return false;
773 }
774 } else if (message instanceof FileRegion) {
775 localWrittenBytes = writeFile(session, req,
776 hasFragmentation, maxWrittenBytes - writtenBytes,
777 currentTime);
778
779
780
781
782 if (localWrittenBytes > 0
783 && ((FileRegion) message).getRemainingBytes() > 0) {
784 writtenBytes += localWrittenBytes;
785 setInterestedInWrite(session, true);
786 return false;
787 }
788 } else {
789 throw new IllegalStateException(
790 "Don't know how to handle message of type '"
791 + message.getClass().getName()
792 + "'. Are you missing a protocol encoder?");
793 }
794
795 if (localWrittenBytes == 0) {
796
797 setInterestedInWrite(session, true);
798 return false;
799 }
800
801 writtenBytes += localWrittenBytes;
802
803 if (writtenBytes >= maxWrittenBytes) {
804
805 scheduleFlush(session);
806 return false;
807 }
808 } while (writtenBytes < maxWrittenBytes);
809 } catch (Exception e) {
810 if (req != null) {
811 req.getFuture().setException(e);
812 }
813 IoFilterChain filterChain = session.getFilterChain();
814 filterChain.fireExceptionCaught(e);
815 return false;
816 }
817
818 return true;
819 }
820
821 private int writeBuffer(T session, WriteRequest req,
822 boolean hasFragmentation, int maxLength, long currentTime)
823 throws Exception {
824 IoBuffer buf = (IoBuffer) req.getMessage();
825 int localWrittenBytes = 0;
826 if (buf.hasRemaining()) {
827 int length;
828 if (hasFragmentation) {
829 length = Math.min(buf.remaining(), maxLength);
830 } else {
831 length = buf.remaining();
832 }
833 for (int i = WRITE_SPIN_COUNT; i > 0; i--) {
834 localWrittenBytes = write(session, buf, length);
835 if (localWrittenBytes != 0) {
836 break;
837 }
838 }
839 }
840
841 session.increaseWrittenBytes(localWrittenBytes, currentTime);
842
843 if (!buf.hasRemaining() || !hasFragmentation && localWrittenBytes != 0) {
844
845 buf.reset();
846 fireMessageSent(session, req);
847 }
848 return localWrittenBytes;
849 }
850
851 private int writeFile(T session, WriteRequest req,
852 boolean hasFragmentation, int maxLength, long currentTime)
853 throws Exception {
854 int localWrittenBytes;
855 FileRegion region = (FileRegion) req.getMessage();
856 if (region.getRemainingBytes() > 0) {
857 int length;
858 if (hasFragmentation) {
859 length = (int) Math.min(region.getRemainingBytes(), maxLength);
860 } else {
861 length = (int) Math.min(Integer.MAX_VALUE, region
862 .getRemainingBytes());
863 }
864 localWrittenBytes = transferFile(session, region, length);
865 region.update(localWrittenBytes);
866 } else {
867 localWrittenBytes = 0;
868 }
869
870 session.increaseWrittenBytes(localWrittenBytes, currentTime);
871
872 if (region.getRemainingBytes() <= 0 || !hasFragmentation
873 && localWrittenBytes != 0) {
874 fireMessageSent(session, req);
875 }
876
877 return localWrittenBytes;
878 }
879
880 private void fireMessageSent(T session, WriteRequest req) {
881 session.setCurrentWriteRequest(null);
882 IoFilterChain filterChain = session.getFilterChain();
883 filterChain.fireMessageSent(req);
884 }
885
886
887
888
889
890 private void updateTrafficMask() {
891 int queueSize = trafficControllingSessions.size();
892
893 while (queueSize > 0) {
894 T session = trafficControllingSessions.poll();
895
896 if (session == null) {
897 return;
898 }
899
900 SessionState state = getState(session);
901
902 switch (state) {
903 case OPENED:
904 updateTrafficControl(session);
905 break;
906
907 case CLOSING:
908 break;
909
910 case OPENING:
911
912
913
914
915 trafficControllingSessions.add(session);
916
917 break;
918
919 default:
920 throw new IllegalStateException(String.valueOf(state));
921 }
922
923
924
925 queueSize--;
926 }
927 }
928
929
930
931
932 public void updateTrafficControl(T session) {
933 try {
934 setInterestedInRead(session, !session.isReadSuspended());
935 } catch (Exception e) {
936 IoFilterChain filterChain = session.getFilterChain();
937 filterChain.fireExceptionCaught(e);
938 }
939
940 try {
941 setInterestedInWrite(session,
942 !session.getWriteRequestQueue().isEmpty(session) &&
943 !session.isWriteSuspended());
944 } catch (Exception e) {
945 IoFilterChain filterChain = session.getFilterChain();
946 filterChain.fireExceptionCaught(e);
947 }
948 }
949
950 private class Processor implements Runnable {
951 public void run() {
952 int nSessions = 0;
953 lastIdleCheckTime = System.currentTimeMillis();
954
955 for (;;) {
956 try {
957
958
959
960
961 int selected = select(SELECT_TIMEOUT);
962
963 nSessions += handleNewSessions();
964 updateTrafficMask();
965
966
967
968 if (selected > 0) {
969 process();
970 }
971
972 long currentTime = System.currentTimeMillis();
973 flush(currentTime);
974 nSessions -= removeSessions();
975 notifyIdleSessions(currentTime);
976
977 if (nSessions == 0) {
978 synchronized (lock) {
979 if (newSessions.isEmpty() && isSelectorEmpty()) {
980 processor = null;
981 break;
982 }
983 }
984 }
985
986
987
988 if (isDisposing()) {
989 for (Iterator<T> i = allSessions(); i.hasNext();) {
990 scheduleRemove(i.next());
991 }
992 wakeup();
993 }
994 } catch (Throwable t) {
995 ExceptionMonitor.getInstance().exceptionCaught(t);
996
997 try {
998 Thread.sleep(1000);
999 } catch (InterruptedException e1) {
1000 ExceptionMonitor.getInstance().exceptionCaught(e1);
1001 }
1002 }
1003 }
1004
1005 try {
1006 synchronized (disposalLock) {
1007 if (isDisposing()) {
1008 dispose0();
1009 }
1010 }
1011 } catch (Throwable t) {
1012 ExceptionMonitor.getInstance().exceptionCaught(t);
1013 } finally {
1014 disposalFuture.setValue(true);
1015 }
1016 }
1017 }
1018 }