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