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