1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *
19 */
20 package org.apache.mina.core.polling;
21
22 import java.io.IOException;
23 import java.net.PortUnreachableException;
24 import java.nio.channels.ClosedSelectorException;
25 import java.util.ArrayList;
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.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentLinkedQueue;
32 import java.util.concurrent.Executor;
33 import java.util.concurrent.atomic.AtomicBoolean;
34 import java.util.concurrent.atomic.AtomicInteger;
35 import java.util.concurrent.atomic.AtomicReference;
36
37 import org.apache.mina.core.buffer.IoBuffer;
38 import org.apache.mina.core.file.FileRegion;
39 import org.apache.mina.core.filterchain.IoFilterChain;
40 import org.apache.mina.core.filterchain.IoFilterChainBuilder;
41 import org.apache.mina.core.future.DefaultIoFuture;
42 import org.apache.mina.core.service.AbstractIoService;
43 import org.apache.mina.core.service.IoProcessor;
44 import org.apache.mina.core.service.IoServiceListenerSupport;
45 import org.apache.mina.core.session.AbstractIoSession;
46 import org.apache.mina.core.session.IoSession;
47 import org.apache.mina.core.session.IoSessionConfig;
48 import org.apache.mina.core.session.SessionState;
49 import org.apache.mina.core.write.WriteRequest;
50 import org.apache.mina.core.write.WriteRequestQueue;
51 import org.apache.mina.core.write.WriteToClosedSessionException;
52 import org.apache.mina.transport.socket.AbstractDatagramSessionConfig;
53 import org.apache.mina.util.ExceptionMonitor;
54 import org.apache.mina.util.NamePreservingRunnable;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59 * An abstract implementation of {@link IoProcessor} which helps transport
60 * developers to write an {@link IoProcessor} easily. This class is in charge of
61 * active polling a set of {@link IoSession} and trigger events when some I/O
62 * operation is possible.
63 *
64 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
65 *
66 * @param <S> the type of the {@link IoSession} this processor can handle
67 */
68 public abstract class AbstractPollingIoProcessor<S extends AbstractIoSession> implements IoProcessor<S> {
69 /** A logger for this class */
70 private final static Logger LOG = LoggerFactory.getLogger(IoProcessor.class);
71
72 /**
73 * The maximum loop count for a write operation until
74 * {@link #write(AbstractIoSession, IoBuffer, int)} returns non-zero value.
75 * It is similar to what a spin lock is for in concurrency programming. It
76 * improves memory utilization and write throughput significantly.
77 */
78 private static final int WRITE_SPIN_COUNT = 256;
79
80 /**
81 * A timeout used for the select, as we need to get out to deal with idle
82 * sessions
83 */
84 private static final long SELECT_TIMEOUT = 1000L;
85
86 /** A map containing the last Thread ID for each class */
87 private static final Map<Class<?>, AtomicInteger> threadIds = new ConcurrentHashMap<Class<?>, AtomicInteger>();
88
89 /** This IoProcessor instance name */
90 private final String threadName;
91
92 /** The executor to use when we need to start the inner Processor */
93 private final Executor executor;
94
95 /** A Session queue containing the newly created sessions */
96 private final Queue<S> newSessions = new ConcurrentLinkedQueue<S>();
97
98 /** A queue used to store the sessions to be removed */
99 private final Queue<S> removingSessions = new ConcurrentLinkedQueue<S>();
100
101 /** A queue used to store the sessions to be flushed */
102 private final Queue<S> flushingSessions = new ConcurrentLinkedQueue<S>();
103
104 /**
105 * A queue used to store the sessions which have a trafficControl to be
106 * updated
107 */
108 private final Queue<S> trafficControllingSessions = new ConcurrentLinkedQueue<S>();
109
110 /** The processor thread : it handles the incoming messages */
111 private final AtomicReference<Processor> processorRef = new AtomicReference<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 * Create an {@link AbstractPollingIoProcessor} with the given
127 * {@link Executor} for handling I/Os events.
128 *
129 * @param executor
130 * the {@link Executor} for handling I/O events
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 * Compute the thread ID for this class instance. As we may have different
143 * classes, we store the last ID number into a Map associating the class
144 * name to the last assigned ID.
145 *
146 * @return a name for the current thread, based on the class name and an
147 * incremental value, starting at 1.
148 */
149 private String nextThreadName() {
150 Class<?> cls = getClass();
151 int newThreadId;
152
153 // We synchronize this block to avoid a concurrent access to
154 // the actomicInteger (it can be modified by another thread, while
155 // being seen as null by another thread)
156 synchronized (threadIds) {
157 // Get the current ID associated to this class' name
158 AtomicInteger threadId = threadIds.get(cls);
159
160 if (threadId == null) {
161 // We never have seen this class before, just create a
162 // new ID starting at 1 for it, and associate this ID
163 // with the class name in the map.
164 newThreadId = 1;
165 threadIds.put(cls, new AtomicInteger(newThreadId));
166 } else {
167 // Just increment the lat ID, and get it.
168 newThreadId = threadId.incrementAndGet();
169 }
170 }
171
172 // Now we can compute the name for this thread
173 return cls.getSimpleName() + '-' + newThreadId;
174 }
175
176 /**
177 * {@inheritDoc}
178 */
179 public final boolean isDisposing() {
180 return disposing;
181 }
182
183 /**
184 * {@inheritDoc}
185 */
186 public final boolean isDisposed() {
187 return disposed;
188 }
189
190 /**
191 * {@inheritDoc}
192 */
193 public final void dispose() {
194 if (disposed || disposing) {
195 return;
196 }
197
198 synchronized (disposalLock) {
199 disposing = true;
200 startupProcessor();
201 }
202
203 disposalFuture.awaitUninterruptibly();
204 disposed = true;
205 }
206
207 /**
208 * Dispose the resources used by this {@link IoProcessor} for polling the
209 * client connections. The implementing class doDispose method will be called.
210 *
211 * @throws Exception if some low level IO error occurs
212 */
213 protected abstract void doDispose() throws Exception;
214
215 /**
216 * poll those sessions for the given timeout
217 *
218 * @param timeout
219 * milliseconds before the call timeout if no event appear
220 * @return The number of session ready for read or for write
221 * @throws Exception
222 * if some low level IO error occurs
223 */
224 protected abstract int select(long timeout) throws Exception;
225
226 /**
227 * poll those sessions forever
228 *
229 * @return The number of session ready for read or for write
230 * @throws Exception
231 * if some low level IO error occurs
232 */
233 protected abstract int select() throws Exception;
234
235 /**
236 * Say if the list of {@link IoSession} polled by this {@link IoProcessor}
237 * is empty
238 *
239 * @return true if at least a session is managed by this {@link IoProcessor}
240 */
241 protected abstract boolean isSelectorEmpty();
242
243 /**
244 * Interrupt the {@link AbstractPollingIoProcessor#select(int) call.
245 */
246 protected abstract void wakeup();
247
248 /**
249 * Get an {@link Iterator} for the list of {@link IoSession} polled by this
250 * {@link IoProcessor}
251 *
252 * @return {@link Iterator} of {@link IoSession}
253 */
254 protected abstract Iterator<S> allSessions();
255
256 /**
257 * Get an {@link Iterator} for the list of {@link IoSession} found selected
258 * by the last call of {@link AbstractPollingIoProcessor#select(int)
259 * @return {@link Iterator} of {@link IoSession} read for I/Os operation
260 */
261 protected abstract Iterator<S> selectedSessions();
262
263 /**
264 * Get the state of a session (preparing, open, closed)
265 *
266 * @param session
267 * the {@link IoSession} to inspect
268 * @return the state of the session
269 */
270 protected abstract SessionState getState(S session);
271
272 /**
273 * Is the session ready for writing
274 *
275 * @param session
276 * the session queried
277 * @return true is ready, false if not ready
278 */
279 protected abstract boolean isWritable(S session);
280
281 /**
282 * Is the session ready for reading
283 *
284 * @param session
285 * the session queried
286 * @return true is ready, false if not ready
287 */
288 protected abstract boolean isReadable(S session);
289
290 /**
291 * register a session for writing
292 *
293 * @param session
294 * the session registered
295 * @param isInterested
296 * true for registering, false for removing
297 */
298 protected abstract void setInterestedInWrite(S session, boolean isInterested)
299 throws Exception;
300
301 /**
302 * register a session for reading
303 *
304 * @param session
305 * the session registered
306 * @param isInterested
307 * true for registering, false for removing
308 */
309 protected abstract void setInterestedInRead(S session, boolean isInterested)
310 throws Exception;
311
312 /**
313 * is this session registered for reading
314 *
315 * @param session
316 * the session queried
317 * @return true is registered for reading
318 */
319 protected abstract boolean isInterestedInRead(S session);
320
321 /**
322 * is this session registered for writing
323 *
324 * @param session
325 * the session queried
326 * @return true is registered for writing
327 */
328 protected abstract boolean isInterestedInWrite(S session);
329
330 /**
331 * Initialize the polling of a session. Add it to the polling process.
332 *
333 * @param session the {@link IoSession} to add to the polling
334 * @throws Exception any exception thrown by the underlying system calls
335 */
336 protected abstract void init(S session) throws Exception;
337
338 /**
339 * Destroy the underlying client socket handle
340 *
341 * @param session
342 * the {@link IoSession}
343 * @throws Exception
344 * any exception thrown by the underlying system calls
345 */
346 protected abstract void destroy(S session) throws Exception;
347
348 /**
349 * Reads a sequence of bytes from a {@link IoSession} into the given
350 * {@link IoBuffer}. Is called when the session was found ready for reading.
351 *
352 * @param session
353 * the session to read
354 * @param buf
355 * the buffer to fill
356 * @return the number of bytes read
357 * @throws Exception
358 * any exception thrown by the underlying system calls
359 */
360 protected abstract int read(S session, IoBuffer buf) throws Exception;
361
362 /**
363 * Write a sequence of bytes to a {@link IoSession}, means to be called when
364 * a session was found ready for writing.
365 *
366 * @param session
367 * the session to write
368 * @param buf
369 * the buffer to write
370 * @param length
371 * the number of bytes to write can be superior to the number of
372 * bytes remaining in the buffer
373 * @return the number of byte written
374 * @throws Exception
375 * any exception thrown by the underlying system calls
376 */
377 protected abstract int write(S session, IoBuffer buf, int length)
378 throws Exception;
379
380 /**
381 * Write a part of a file to a {@link IoSession}, if the underlying API
382 * isn't supporting system calls like sendfile(), you can throw a
383 * {@link UnsupportedOperationException} so the file will be send using
384 * usual {@link #write(AbstractIoSession, IoBuffer, int)} call.
385 *
386 * @param session
387 * the session to write
388 * @param region
389 * the file region to write
390 * @param length
391 * the length of the portion to send
392 * @return the number of written bytes
393 * @throws Exception
394 * any exception thrown by the underlying system calls
395 */
396 protected abstract int transferFile(S session, FileRegion region, int length)
397 throws Exception;
398
399 /**
400 * {@inheritDoc}
401 */
402 public final void add(S session) {
403 if (disposed || disposing) {
404 throw new IllegalStateException("Already disposed.");
405 }
406
407 // Adds the session to the newSession queue and starts the worker
408 newSessions.add(session);
409 startupProcessor();
410 }
411
412 /**
413 * {@inheritDoc}
414 */
415 public final void remove(S session) {
416 scheduleRemove(session);
417 startupProcessor();
418 }
419
420 private void scheduleRemove(S session) {
421 removingSessions.add(session);
422 }
423
424 /**
425 * {@inheritDoc}
426 */
427 public final void flush(S session) {
428 // add the session to the queue if it's not already
429 // in the queue, then wake up the select()
430 if (session.setScheduledForFlush( true )) {
431 flushingSessions.add(session);
432 wakeup();
433 }
434 }
435
436 private void scheduleFlush(S session) {
437 // add the session to the queue if it's not already
438 // in the queue
439 if (session.setScheduledForFlush(true)) {
440 flushingSessions.add(session);
441 }
442 }
443
444 /**
445 * {@inheritDoc}
446 */
447 public final void updateTrafficMask(S session) {
448 trafficControllingSessions.add(session);
449 wakeup();
450 }
451
452 /**
453 * Starts the inner Processor, asking the executor to pick a thread in its
454 * pool. The Runnable will be renamed
455 */
456 private void startupProcessor() {
457 Processor processor = processorRef.get();
458
459 if (processor == null) {
460 processor = new Processor();
461
462 if (processorRef.compareAndSet(null, processor)) {
463 executor.execute(new NamePreservingRunnable(processor, threadName));
464 }
465 }
466
467 // Just stop the select() and start it again, so that the processor
468 // can be activated immediately.
469 wakeup();
470 }
471
472 /**
473 * In the case we are using the java select() method, this method is used to
474 * trash the buggy selector and create a new one, registring all the sockets
475 * on it.
476 *
477 * @throws IOException
478 * If we got an exception
479 */
480 abstract protected void registerNewSelector() throws IOException;
481
482 /**
483 * Check that the select() has not exited immediately just because of a
484 * broken connection. In this case, this is a standard case, and we just
485 * have to loop.
486 *
487 * @return true if a connection has been brutally closed.
488 * @throws IOException
489 * If we got an exception
490 */
491 abstract protected boolean isBrokenConnection() throws IOException;
492
493 /**
494 * Loops over the new sessions blocking queue and returns the number of
495 * sessions which are effectively created
496 *
497 * @return The number of new sessions
498 */
499 private int handleNewSessions() {
500 int addedSessions = 0;
501
502 for (S session = newSessions.poll(); session != null; session = newSessions.poll()) {
503 if (addNow(session)) {
504 // A new session has been created
505 addedSessions++;
506 }
507 }
508
509 return addedSessions;
510 }
511
512 /**
513 * Process a new session :
514 * - initialize it
515 * - create its chain
516 * - fire the CREATED listeners if any
517 *
518 * @param session The session to create
519 * @return true if the session has been registered
520 */
521 private boolean addNow(S session) {
522 boolean registered = false;
523
524 try {
525 init(session);
526 registered = true;
527
528 // Build the filter chain of this session.
529 IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder();
530 chainBuilder.buildFilterChain(session.getFilterChain());
531
532 // DefaultIoFilterChain.CONNECT_FUTURE is cleared inside here
533 // in AbstractIoFilterChain.fireSessionOpened().
534 // Propagate the SESSION_CREATED event up to the chain
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 (S session = removingSessions.poll(); session != null; session = removingSessions.poll()) {
556 SessionState state = getState(session);
557
558 // Now deal with the removal accordingly to the session's state
559 switch (state) {
560 case OPENED:
561 // Try to remove this session
562 if (removeNow(session)) {
563 removedSessions++;
564 }
565
566 break;
567
568 case CLOSING:
569 // Skip if channel is already closed
570 break;
571
572 case OPENING:
573 // Remove session from the newSessions queue and
574 // remove it
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(S 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(S 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 // The first unwritten empty buffer must be
621 // forwarded to the filter chain.
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 // Discard others.
634 while ((req = writeRequestQueue.poll(session)) != null) {
635 failedRequests.add(req);
636 }
637 }
638
639 // Create an exception and notify.
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<S> i = selectedSessions(); i.hasNext();) {
656 S session = i.next();
657 process(session);
658 i.remove();
659 }
660 }
661
662 /**
663 * Deal with session ready for the read or write operations, or both.
664 */
665 private void process(S session) {
666 // Process Reads
667 if (isReadable(session) && !session.isReadSuspended()) {
668 read(session);
669 }
670
671 // Process writes
672 if (isWritable(session) && !session.isWriteSuspended()) {
673 // add the session to the queue, if it's not already there
674 if (session.setScheduledForFlush(true)) {
675 flushingSessions.add(session);
676 }
677 }
678 }
679
680 private void read(S 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
745 private static String byteArrayToHex( byte[] barray )
746 {
747 char[] c = new char[barray.length * 2];
748 int pos = 0;
749
750 for ( byte b : barray )
751 {
752 int bb = ( b & 0x00FF ) >> 4;
753 c[pos++] = ( char ) ( bb > 9 ? bb + 0x37 : bb + 0x30 );
754 bb = b & 0x0F;
755 c[pos++] = ( char ) ( bb > 9 ? bb + 0x37 : bb + 0x30 );
756 if ( pos > 60 )
757 {
758 break;
759 }
760 }
761
762 return new String( c );
763 }
764
765
766 private void notifyIdleSessions(long currentTime) throws Exception {
767 // process idle sessions
768 if (currentTime - lastIdleCheckTime >= SELECT_TIMEOUT) {
769 lastIdleCheckTime = currentTime;
770 AbstractIoSession.notifyIdleness(allSessions(), currentTime);
771 }
772 }
773
774 /**
775 * Write all the pending messages
776 */
777 private void flush(long currentTime) {
778 if (flushingSessions.isEmpty()) {
779 return;
780 }
781
782 do {
783 S session = flushingSessions.poll(); // the same one with firstSession
784
785 if (session == null) {
786 // Just in case ... It should not happen.
787 break;
788 }
789
790 // Reset the Schedule for flush flag for this session,
791 // as we are flushing it now
792 session.unscheduledForFlush();
793
794 SessionState state = getState(session);
795
796 switch (state) {
797 case OPENED:
798 try {
799 boolean flushedAll = flushNow(session, currentTime);
800
801 if (flushedAll
802 && !session.getWriteRequestQueue().isEmpty(session)
803 && !session.isScheduledForFlush()) {
804 scheduleFlush(session);
805 }
806 } catch (Exception e) {
807 scheduleRemove(session);
808 IoFilterChain filterChain = session.getFilterChain();
809 filterChain.fireExceptionCaught(e);
810 }
811
812 break;
813
814 case CLOSING:
815 // Skip if the channel is already closed.
816 break;
817
818 case OPENING:
819 // Retry later if session is not yet fully initialized.
820 // (In case that Session.write() is called before addSession()
821 // is processed)
822 scheduleFlush(session);
823 return;
824
825 default:
826 throw new IllegalStateException(String.valueOf(state));
827 }
828
829 } while (!flushingSessions.isEmpty());
830 }
831
832 private boolean flushNow(S session, long currentTime) {
833 if (!session.isConnected()) {
834 scheduleRemove(session);
835 return false;
836 }
837
838 final boolean hasFragmentation = session.getTransportMetadata()
839 .hasFragmentation();
840
841 final WriteRequestQueue writeRequestQueue = session
842 .getWriteRequestQueue();
843
844 // Set limitation for the number of written bytes for read-write
845 // fairness. I used maxReadBufferSize * 3 / 2, which yields best
846 // performance in my experience while not breaking fairness much.
847 final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize()
848 + (session.getConfig().getMaxReadBufferSize() >>> 1);
849 int writtenBytes = 0;
850 WriteRequest req = null;
851
852 try {
853 // Clear OP_WRITE
854 setInterestedInWrite(session, false);
855
856 do {
857 // Check for pending writes.
858 req = session.getCurrentWriteRequest();
859
860 if (req == null) {
861 req = writeRequestQueue.poll(session);
862
863 if (req == null) {
864 break;
865 }
866
867 session.setCurrentWriteRequest(req);
868 }
869
870 int localWrittenBytes = 0;
871 Object message = req.getMessage();
872
873 if (message instanceof IoBuffer) {
874 localWrittenBytes = writeBuffer(session, req,
875 hasFragmentation, maxWrittenBytes - writtenBytes,
876 currentTime);
877
878 if (( localWrittenBytes > 0 )
879 && ((IoBuffer) message).hasRemaining()) {
880 // the buffer isn't empty, we re-interest it in writing
881 writtenBytes += localWrittenBytes;
882 setInterestedInWrite(session, true);
883 return false;
884 }
885 } else if (message instanceof FileRegion) {
886 localWrittenBytes = writeFile(session, req,
887 hasFragmentation, maxWrittenBytes - writtenBytes,
888 currentTime);
889
890 // Fix for Java bug on Linux
891 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988
892 // If there's still data to be written in the FileRegion,
893 // return 0 indicating that we need
894 // to pause until writing may resume.
895 if (( localWrittenBytes > 0 )
896 && ( ((FileRegion) message).getRemainingBytes() > 0 )) {
897 writtenBytes += localWrittenBytes;
898 setInterestedInWrite(session, true);
899 return false;
900 }
901 } else {
902 throw new IllegalStateException(
903 "Don't know how to handle message of type '"
904 + message.getClass().getName()
905 + "'. Are you missing a protocol encoder?");
906 }
907
908 if (localWrittenBytes == 0) {
909 // Kernel buffer is full.
910 setInterestedInWrite(session, true);
911 return false;
912 }
913
914 writtenBytes += localWrittenBytes;
915
916 if (writtenBytes >= maxWrittenBytes) {
917 // Wrote too much
918 scheduleFlush(session);
919 return false;
920 }
921 } while (writtenBytes < maxWrittenBytes);
922 } catch (Exception e) {
923 if (req != null) {
924 req.getFuture().setException(e);
925 }
926
927 IoFilterChain filterChain = session.getFilterChain();
928 filterChain.fireExceptionCaught(e);
929 return false;
930 }
931
932 return true;
933 }
934
935 private int writeBuffer(S session, WriteRequest req,
936 boolean hasFragmentation, int maxLength, long currentTime)
937 throws Exception {
938 IoBuffer buf = (IoBuffer) req.getMessage();
939 int localWrittenBytes = 0;
940
941 if (buf.hasRemaining()) {
942 int length;
943
944 if (hasFragmentation) {
945 length = Math.min(buf.remaining(), maxLength);
946 } else {
947 length = buf.remaining();
948 }
949
950 localWrittenBytes = write(session, buf, length);
951 }
952
953 session.increaseWrittenBytes(localWrittenBytes, currentTime);
954
955 if (!buf.hasRemaining() || ( !hasFragmentation && ( localWrittenBytes != 0 ) )) {
956 // Buffer has been sent, clear the current request.
957 int pos = buf.position();
958 buf.reset();
959
960 fireMessageSent(session, req);
961
962 // And set it back to its position
963 buf.position(pos);
964 }
965 return localWrittenBytes;
966 }
967
968 private int writeFile(S session, WriteRequest req,
969 boolean hasFragmentation, int maxLength, long currentTime)
970 throws Exception {
971 int localWrittenBytes;
972 FileRegion region = (FileRegion) req.getMessage();
973
974 if (region.getRemainingBytes() > 0) {
975 int length;
976
977 if (hasFragmentation) {
978 length = (int) Math.min(region.getRemainingBytes(), maxLength);
979 } else {
980 length = (int) Math.min(Integer.MAX_VALUE, region
981 .getRemainingBytes());
982 }
983
984 localWrittenBytes = transferFile(session, region, length);
985 region.update(localWrittenBytes);
986 } else {
987 localWrittenBytes = 0;
988 }
989
990 session.increaseWrittenBytes(localWrittenBytes, currentTime);
991
992 if (( region.getRemainingBytes() <= 0 ) || ( !hasFragmentation
993 && ( localWrittenBytes != 0 ) )) {
994 fireMessageSent(session, req);
995 }
996
997 return localWrittenBytes;
998 }
999
1000 private void fireMessageSent(S session, WriteRequest req) {
1001 session.setCurrentWriteRequest(null);
1002 IoFilterChain filterChain = session.getFilterChain();
1003 filterChain.fireMessageSent(req);
1004 }
1005
1006 /**
1007 * Update the trafficControl for all the session.
1008 */
1009 private void updateTrafficMask() {
1010 int queueSize = trafficControllingSessions.size();
1011
1012 while (queueSize > 0) {
1013 S session = trafficControllingSessions.poll();
1014
1015 if (session == null) {
1016 // We are done with this queue.
1017 return;
1018 }
1019
1020 SessionState state = getState(session);
1021
1022 switch (state) {
1023 case OPENED:
1024 updateTrafficControl(session);
1025
1026 break;
1027
1028 case CLOSING:
1029 break;
1030
1031 case OPENING:
1032 // Retry later if session is not yet fully initialized.
1033 // (In case that Session.suspend??() or session.resume??() is
1034 // called before addSession() is processed)
1035 // We just put back the session at the end of the queue.
1036 trafficControllingSessions.add(session);
1037 break;
1038
1039 default:
1040 throw new IllegalStateException(String.valueOf(state));
1041 }
1042
1043 // As we have handled one session, decrement the number of
1044 // remaining sessions. The OPENING session will be processed
1045 // with the next select(), as the queue size has been decreased, even
1046 // if the session has been pushed at the end of the queue
1047 queueSize--;
1048 }
1049 }
1050
1051 /**
1052 * {@inheritDoc}
1053 */
1054 public void updateTrafficControl(S session) {
1055 //
1056 try {
1057 setInterestedInRead(session, !session.isReadSuspended());
1058 } catch (Exception e) {
1059 IoFilterChain filterChain = session.getFilterChain();
1060 filterChain.fireExceptionCaught(e);
1061 }
1062
1063 try {
1064 setInterestedInWrite(session, !session.getWriteRequestQueue()
1065 .isEmpty(session)
1066 && !session.isWriteSuspended());
1067 } catch (Exception e) {
1068 IoFilterChain filterChain = session.getFilterChain();
1069 filterChain.fireExceptionCaught(e);
1070 }
1071 }
1072
1073 /**
1074 * The main loop. This is the place in charge to poll the Selector, and to
1075 * process the active sessions. It's done in
1076 * - handle the newly created sessions
1077 * -
1078 */
1079 private class Processor implements Runnable {
1080 public void run() {
1081 assert (processorRef.get() == this);
1082
1083 int nSessions = 0;
1084 lastIdleCheckTime = System.currentTimeMillis();
1085
1086 for (;;) {
1087 try {
1088 // This select has a timeout so that we can manage
1089 // idle session when we get out of the select every
1090 // second. (note : this is a hack to avoid creating
1091 // a dedicated thread).
1092 long t0 = System.currentTimeMillis();
1093 int selected = select(SELECT_TIMEOUT);
1094 long t1 = System.currentTimeMillis();
1095 long delta = (t1 - t0);
1096
1097 if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) {
1098 // Last chance : the select() may have been
1099 // interrupted because we have had an closed channel.
1100 if (isBrokenConnection()) {
1101 LOG.warn("Broken connection");
1102
1103 // we can reselect immediately
1104 // set back the flag to false
1105 wakeupCalled.getAndSet(false);
1106
1107 continue;
1108 } else {
1109 LOG.warn("Create a new selector. Selected is 0, delta = "
1110 + (t1 - t0));
1111 // Ok, we are hit by the nasty epoll
1112 // spinning.
1113 // Basically, there is a race condition
1114 // which causes a closing file descriptor not to be
1115 // considered as available as a selected channel, but
1116 // it stopped the select. The next time we will
1117 // call select(), it will exit immediately for the same
1118 // reason, and do so forever, consuming 100%
1119 // CPU.
1120 // We have to destroy the selector, and
1121 // register all the socket on a new one.
1122 registerNewSelector();
1123 }
1124
1125 // Set back the flag to false
1126 wakeupCalled.getAndSet(false);
1127
1128 // and continue the loop
1129 continue;
1130 }
1131
1132 // Manage newly created session first
1133 nSessions += handleNewSessions();
1134
1135 updateTrafficMask();
1136
1137 // Now, if we have had some incoming or outgoing events,
1138 // deal with them
1139 if (selected > 0) {
1140 //LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test...
1141 process();
1142 }
1143
1144 // Write the pending requests
1145 long currentTime = System.currentTimeMillis();
1146 flush(currentTime);
1147
1148 // And manage removed sessions
1149 nSessions -= removeSessions();
1150
1151 // Last, not least, send Idle events to the idle sessions
1152 notifyIdleSessions(currentTime);
1153
1154 // Get a chance to exit the infinite loop if there are no
1155 // more sessions on this Processor
1156 if (nSessions == 0) {
1157 processorRef.set(null);
1158
1159 if (newSessions.isEmpty() && isSelectorEmpty()) {
1160 // newSessions.add() precedes startupProcessor
1161 assert (processorRef.get() != this);
1162 break;
1163 }
1164
1165 assert (processorRef.get() != this);
1166
1167 if (!processorRef.compareAndSet(null, this)) {
1168 // startupProcessor won race, so must exit processor
1169 assert (processorRef.get() != this);
1170 break;
1171 }
1172
1173 assert (processorRef.get() == this);
1174 }
1175
1176 // Disconnect all sessions immediately if disposal has been
1177 // requested so that we exit this loop eventually.
1178 if (isDisposing()) {
1179 for (Iterator<S> i = allSessions(); i.hasNext();) {
1180 scheduleRemove(i.next());
1181 }
1182
1183 wakeup();
1184 }
1185 } catch (ClosedSelectorException cse) {
1186 // If the selector has been closed, we can exit the loop
1187 break;
1188 } catch (Throwable t) {
1189 ExceptionMonitor.getInstance().exceptionCaught(t);
1190
1191 try {
1192 Thread.sleep(1000);
1193 } catch (InterruptedException e1) {
1194 ExceptionMonitor.getInstance().exceptionCaught(e1);
1195 }
1196 }
1197 }
1198
1199 try {
1200 synchronized (disposalLock) {
1201 if (disposing) {
1202 doDispose();
1203 }
1204 }
1205 } catch (Throwable t) {
1206 ExceptionMonitor.getInstance().exceptionCaught(t);
1207 } finally {
1208 disposalFuture.setValue(true);
1209 }
1210 }
1211 }
1212 }