View Javadoc

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.net.Inet4Address;
23  import java.net.Inet6Address;
24  import java.net.InetAddress;
25  import java.net.InetSocketAddress;
26  import java.net.SocketAddress;
27  import java.nio.channels.ClosedSelectorException;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Queue;
35  import java.util.Set;
36  import java.util.concurrent.ConcurrentLinkedQueue;
37  import java.util.concurrent.Executor;
38  
39  import org.apache.mina.core.RuntimeIoException;
40  import org.apache.mina.core.buffer.IoBuffer;
41  import org.apache.mina.core.service.AbstractIoAcceptor;
42  import org.apache.mina.core.service.IoAcceptor;
43  import org.apache.mina.core.service.IoProcessor;
44  import org.apache.mina.core.session.AbstractIoSession;
45  import org.apache.mina.core.session.ExpiringSessionRecycler;
46  import org.apache.mina.core.session.IoSession;
47  import org.apache.mina.core.session.IoSessionConfig;
48  import org.apache.mina.core.session.IoSessionRecycler;
49  import org.apache.mina.core.write.WriteRequest;
50  import org.apache.mina.core.write.WriteRequestQueue;
51  import org.apache.mina.util.ExceptionMonitor;
52  
53  /**
54   * {@link IoAcceptor} for datagram transport (UDP/IP).
55   *
56   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
57   * @org.apache.xbean.XBean
58   *
59    * @param <S> the type of the {@link IoSession} this processor can handle
60  */
61  public abstract class AbstractPollingConnectionlessIoAcceptor<S extends AbstractIoSession, H>
62          extends AbstractIoAcceptor {
63  
64      private static final IoSessionRecycler DEFAULT_RECYCLER = new ExpiringSessionRecycler();
65  
66      /**
67       * A timeout used for the select, as we need to get out to deal with idle
68       * sessions
69       */
70      private static final long SELECT_TIMEOUT = 1000L;
71  
72      private final Object lock = new Object();
73  
74      private final IoProcessor<S> processor = new ConnectionlessAcceptorProcessor();
75      private final Queue<AcceptorOperationFuture> registerQueue =
76          new ConcurrentLinkedQueue<AcceptorOperationFuture>();
77      private final Queue<AcceptorOperationFuture> cancelQueue =
78          new ConcurrentLinkedQueue<AcceptorOperationFuture>();
79  
80      private final Queue<S> flushingSessions = new ConcurrentLinkedQueue<S>();
81      private final Map<String, H> boundHandles =
82          Collections.synchronizedMap(new HashMap<String, H>());
83  
84      private IoSessionRecycler sessionRecycler = DEFAULT_RECYCLER;
85  
86      private final ServiceOperationFuture disposalFuture =
87          new ServiceOperationFuture();
88      private volatile boolean selectable;
89  
90      /** The thread responsible of accepting incoming requests */
91      private Acceptor acceptor;
92  
93      private long lastIdleCheckTime;
94  
95      private String getAddressAsString(SocketAddress address) {
96          InetAddress inetAddress = ((InetSocketAddress)address).getAddress();
97          int port = ((InetSocketAddress)address).getPort();
98  
99          if (inetAddress == null) {
100             return "null";
101         }
102 
103         String result = null;
104 
105         if ( inetAddress instanceof Inet4Address ) {
106             result = "/" + inetAddress.getHostAddress() + ":" + port;
107         } else {
108             // Inet6
109             if ( ((Inet6Address)inetAddress).isIPv4CompatibleAddress() ) {
110                 byte[] bytes = inetAddress.getAddress();
111 
112                 result = "/" + bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15] + ":" + port;
113             } else {
114                 result = inetAddress.toString();
115             }
116         }
117 
118         return result;
119     }
120 
121     /**
122      * Creates a new instance.
123      */
124     protected AbstractPollingConnectionlessIoAcceptor(IoSessionConfig sessionConfig) {
125         this(sessionConfig, null);
126     }
127 
128     /**
129      * Creates a new instance.
130      */
131     protected AbstractPollingConnectionlessIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {
132         super(sessionConfig, executor);
133 
134         try {
135             init();
136             selectable = true;
137         } catch (RuntimeException e) {
138             throw e;
139         } catch (Exception e) {
140             throw new RuntimeIoException("Failed to initialize.", e);
141         } finally {
142             if (!selectable) {
143                 try {
144                     destroy();
145                 } catch (Exception e) {
146                     ExceptionMonitor.getInstance().exceptionCaught(e);
147                 }
148             }
149         }
150     }
151 
152     protected abstract void init() throws Exception;
153     protected abstract void destroy() throws Exception;
154     protected abstract int select() throws Exception;
155     protected abstract int select(long timeout) throws Exception;
156     protected abstract void wakeup();
157     protected abstract Iterator<H> selectedHandles();
158     protected abstract H open(SocketAddress localAddress) throws Exception;
159     protected abstract void close(H handle) throws Exception;
160     protected abstract SocketAddress localAddress(H handle) throws Exception;
161     protected abstract boolean isReadable(H handle);
162     protected abstract boolean isWritable(H handle);
163     protected abstract SocketAddress receive(H handle, IoBuffer buffer) throws Exception;
164 
165     protected abstract int send(S session, IoBuffer buffer, SocketAddress remoteAddress) throws Exception;
166 
167     protected abstract S newSession(IoProcessor<S> processor, H handle, SocketAddress remoteAddress) throws Exception;
168 
169     protected abstract void setInterestedInWrite(S session, boolean interested) throws Exception;
170 
171     /**
172      * {@inheritDoc}
173      */
174     @Override
175     protected void dispose0() throws Exception {
176         unbind();
177         startupAcceptor();
178         wakeup();
179     }
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
185     protected final Set<SocketAddress> bindInternal(
186             List<? extends SocketAddress> localAddresses) throws Exception {
187         // Create a bind request as a Future operation. When the selector
188         // have handled the registration, it will signal this future.
189         AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);
190 
191         // adds the Registration request to the queue for the Workers
192         // to handle
193         registerQueue.add(request);
194 
195         // creates the Acceptor instance and has the local
196         // executor kick it off.
197         startupAcceptor();
198 
199         // As we just started the acceptor, we have to unblock the select()
200         // in order to process the bind request we just have added to the
201         // registerQueue.
202         wakeup();
203 
204         // Now, we wait until this request is completed.
205         request.awaitUninterruptibly();
206 
207         if (request.getException() != null) {
208             throw request.getException();
209         }
210 
211         // Update the local addresses.
212         // setLocalAddresses() shouldn't be called from the worker thread
213         // because of deadlock.
214         Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();
215 
216         for (H handle : boundHandles.values()) {
217             newLocalAddresses.add(localAddress(handle));
218         }
219 
220         return newLocalAddresses;
221     }
222 
223     /**
224      * {@inheritDoc}
225      */
226     @Override
227     protected final void unbind0(List<? extends SocketAddress> localAddresses) throws Exception {
228         AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);
229 
230         cancelQueue.add(request);
231         startupAcceptor();
232         wakeup();
233 
234         request.awaitUninterruptibly();
235 
236         if (request.getException() != null) {
237             throw request.getException();
238         }
239     }
240 
241     /**
242      * {@inheritDoc}
243      */
244     public final IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress) {
245         if (isDisposing()) {
246             throw new IllegalStateException("Already disposed.");
247         }
248 
249         if (remoteAddress == null) {
250             throw new IllegalArgumentException("remoteAddress");
251         }
252 
253         synchronized (bindLock) {
254             if (!isActive()) {
255                 throw new IllegalStateException(
256                         "Can't create a session from a unbound service.");
257             }
258 
259             try {
260                 return newSessionWithoutLock(remoteAddress, localAddress);
261             } catch (RuntimeException e) {
262                 throw e;
263             } catch (Error e) {
264                 throw e;
265             } catch (Exception e) {
266                 throw new RuntimeIoException("Failed to create a session.", e);
267             }
268         }
269     }
270 
271     private IoSession newSessionWithoutLock(
272             SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
273         H handle = boundHandles.get(getAddressAsString(localAddress));
274 
275         if (handle == null) {
276             throw new IllegalArgumentException("Unknown local address: " + localAddress);
277         }
278 
279         IoSession session;
280         IoSessionRecycler sessionRecycler = getSessionRecycler();
281 
282         synchronized (sessionRecycler) {
283             session = sessionRecycler.recycle(localAddress, remoteAddress);
284 
285             if (session != null) {
286                 return session;
287             }
288 
289             // If a new session needs to be created.
290             S newSession = newSession(processor, handle, remoteAddress);
291             getSessionRecycler().put(newSession);
292             session = newSession;
293         }
294 
295         initSession(session, null, null);
296 
297         try {
298             this.getFilterChainBuilder().buildFilterChain(session.getFilterChain());
299             getListeners().fireSessionCreated(session);
300         } catch (Throwable t) {
301             ExceptionMonitor.getInstance().exceptionCaught(t);
302         }
303 
304         return session;
305     }
306 
307     public final IoSessionRecycler getSessionRecycler() {
308         return sessionRecycler;
309     }
310 
311     public final void setSessionRecycler(IoSessionRecycler sessionRecycler) {
312         synchronized (bindLock) {
313             if (isActive()) {
314                 throw new IllegalStateException(
315                         "sessionRecycler can't be set while the acceptor is bound.");
316             }
317 
318             if (sessionRecycler == null) {
319                 sessionRecycler = DEFAULT_RECYCLER;
320             }
321 
322             this.sessionRecycler = sessionRecycler;
323         }
324     }
325 
326     private class ConnectionlessAcceptorProcessor implements IoProcessor<S> {
327 
328         public void add(S session) {
329         }
330 
331         public void flush(S session) {
332             if (scheduleFlush(session)) {
333                 wakeup();
334             }
335         }
336 
337         public void remove(S session) {
338             getSessionRecycler().remove(session);
339             getListeners().fireSessionDestroyed(session);
340         }
341 
342         public void updateTrafficControl(S session) {
343             throw new UnsupportedOperationException();
344         }
345 
346         public void dispose() {
347         }
348 
349         public boolean isDisposed() {
350             return false;
351         }
352 
353         public boolean isDisposing() {
354             return false;
355         }
356     }
357 
358     /**
359      * Starts the inner Acceptor thread.
360      */
361     private void startupAcceptor() {
362         if (!selectable) {
363             registerQueue.clear();
364             cancelQueue.clear();
365             flushingSessions.clear();
366         }
367 
368         synchronized (lock) {
369             if (acceptor == null) {
370                 acceptor = new Acceptor();
371                 executeWorker(acceptor);
372             }
373         }
374     }
375 
376     private boolean scheduleFlush(S session) {
377         // Set the schedule for flush flag if the session
378         // has not already be added to the flushingSessions
379         // queue
380         if (session.setScheduledForFlush(true)) {
381             flushingSessions.add(session);
382             return true;
383         } else {
384             return false;
385         }
386     }
387 
388     /**
389      * This private class is used to accept incoming connection from
390      * clients. It's an infinite loop, which can be stopped when all
391      * the registered handles have been removed (unbound).
392      */
393     private class Acceptor implements Runnable {
394         public void run() {
395             int nHandles = 0;
396             lastIdleCheckTime = System.currentTimeMillis();
397 
398             while (selectable) {
399                 try {
400                     int selected = select(SELECT_TIMEOUT);
401 
402                     nHandles += registerHandles();
403 
404                     if (nHandles == 0) {
405                         synchronized (lock) {
406                             if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {
407                                 acceptor = null;
408                                 break;
409                             }
410                         }
411                     }
412 
413                     if (selected > 0) {
414                         processReadySessions(selectedHandles());
415                     }
416 
417                     long currentTime = System.currentTimeMillis();
418                     flushSessions(currentTime);
419                     nHandles -= unregisterHandles();
420 
421                     notifyIdleSessions(currentTime);
422                 } catch (ClosedSelectorException cse) {
423                     // If the selector has been closed, we can exit the loop
424                     break;
425                 } catch (Exception e) {
426                     ExceptionMonitor.getInstance().exceptionCaught(e);
427 
428                     try {
429                         Thread.sleep(1000);
430                     } catch (InterruptedException e1) {
431                     }
432                 }
433             }
434 
435             if (selectable && isDisposing()) {
436                 selectable = false;
437                 try {
438                     destroy();
439                 } catch (Exception e) {
440                     ExceptionMonitor.getInstance().exceptionCaught(e);
441                 } finally {
442                     disposalFuture.setValue(true);
443                 }
444             }
445         }
446     }
447 
448     @SuppressWarnings("unchecked")
449     private void processReadySessions(Iterator<H> handles) {
450         while (handles.hasNext()) {
451             H h = handles.next();
452             handles.remove();
453 
454             try {
455                 if (isReadable(h)) {
456                     readHandle(h);
457                 }
458 
459                 if (isWritable(h)) {
460                     for (IoSession session : getManagedSessions().values()) {
461                         scheduleFlush((S) session);
462                     }
463                 }
464             } catch (Throwable t) {
465                 ExceptionMonitor.getInstance().exceptionCaught(t);
466             }
467         }
468     }
469 
470     private void readHandle(H handle) throws Exception {
471         IoBuffer readBuf = IoBuffer.allocate(
472                 getSessionConfig().getReadBufferSize());
473 
474         SocketAddress remoteAddress = receive(handle, readBuf);
475 
476         if (remoteAddress != null) {
477             IoSession session = newSessionWithoutLock(
478                     remoteAddress, localAddress(handle));
479 
480             readBuf.flip();
481 
482             IoBuffer newBuf = IoBuffer.allocate(readBuf.limit());
483             newBuf.put(readBuf);
484             newBuf.flip();
485 
486             session.getFilterChain().fireMessageReceived(newBuf);
487         }
488     }
489 
490     private void flushSessions(long currentTime) {
491         for (;;) {
492             S session = flushingSessions.poll();
493 
494             if (session == null) {
495                 break;
496             }
497 
498             // Reset the Schedule for flush flag for this session,
499             // as we are flushing it now
500             session.unscheduledForFlush();
501 
502             try {
503                 boolean flushedAll = flush(session, currentTime);
504                 if (flushedAll && !session.getWriteRequestQueue().isEmpty(session) &&
505                     !session.isScheduledForFlush()) {
506                     scheduleFlush(session);
507                 }
508             } catch (Exception e) {
509                 session.getFilterChain().fireExceptionCaught(e);
510             }
511         }
512     }
513 
514     private boolean flush(S session, long currentTime) throws Exception {
515         // Clear OP_WRITE
516         setInterestedInWrite(session, false);
517 
518         final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
519         final int maxWrittenBytes =
520             session.getConfig().getMaxReadBufferSize() +
521             (session.getConfig().getMaxReadBufferSize() >>> 1);
522 
523         int writtenBytes = 0;
524 
525         try {
526             for (;;) {
527                 WriteRequest req = session.getCurrentWriteRequest();
528 
529                 if (req == null) {
530                     req = writeRequestQueue.poll(session);
531                     if (req == null) {
532                         break;
533                     }
534                     session.setCurrentWriteRequest(req);
535                 }
536 
537                 IoBuffer buf = (IoBuffer) req.getMessage();
538 
539                 if (buf.remaining() == 0) {
540                     // Clear and fire event
541                     session.setCurrentWriteRequest(null);
542                     buf.reset();
543                     session.getFilterChain().fireMessageSent(req);
544                     continue;
545                 }
546 
547                 SocketAddress destination = req.getDestination();
548 
549                 if (destination == null) {
550                     destination = session.getRemoteAddress();
551                 }
552 
553                 int localWrittenBytes = send(session, buf, destination);
554 
555                 if (( localWrittenBytes == 0 ) || ( writtenBytes >= maxWrittenBytes )) {
556                     // Kernel buffer is full or wrote too much
557                     setInterestedInWrite(session, true);
558                     return false;
559                 } else {
560                     setInterestedInWrite(session, false);
561 
562                     // Clear and fire event
563                     session.setCurrentWriteRequest(null);
564                     writtenBytes += localWrittenBytes;
565                     buf.reset();
566                     session.getFilterChain().fireMessageSent(req);
567                 }
568             }
569         } finally {
570             session.increaseWrittenBytes(writtenBytes, currentTime);
571         }
572 
573         return true;
574     }
575 
576     private int registerHandles() {
577         for (;;) {
578             AcceptorOperationFuture req = registerQueue.poll();
579 
580             if (req == null) {
581                 break;
582             }
583 
584             Map<String, H> newHandles = new HashMap<String, H>();
585             List<SocketAddress> localAddresses = req.getLocalAddresses();
586 
587             try {
588                 for (SocketAddress socketAddress : localAddresses) {
589                     H handle = open(socketAddress);
590                     newHandles.put(getAddressAsString(localAddress(handle)), handle);
591                 }
592 
593                 boundHandles.putAll(newHandles);
594 
595                 getListeners().fireServiceActivated();
596                 req.setDone();
597 
598                 return newHandles.size();
599             } catch (Exception e) {
600                 req.setException(e);
601             } finally {
602                 // Roll back if failed to bind all addresses.
603                 if (req.getException() != null) {
604                     for (H handle : newHandles.values()) {
605                         try {
606                             close(handle);
607                         } catch (Exception e) {
608                             ExceptionMonitor.getInstance().exceptionCaught(e);
609                         }
610                     }
611 
612                     wakeup();
613                 }
614             }
615         }
616 
617         return 0;
618     }
619 
620     private int unregisterHandles() {
621         int nHandles = 0;
622 
623         for (;;) {
624             AcceptorOperationFuture request = cancelQueue.poll();
625             if (request == null) {
626                 break;
627             }
628 
629             // close the channels
630             for (SocketAddress socketAddress : request.getLocalAddresses()) {
631                 H handle = boundHandles.remove(getAddressAsString(socketAddress));
632 
633                 if (handle == null) {
634                     continue;
635                 }
636 
637                 try {
638                     close(handle);
639                     wakeup(); // wake up again to trigger thread death
640                 } catch (Throwable e) {
641                     ExceptionMonitor.getInstance().exceptionCaught(e);
642                 } finally {
643                     nHandles++;
644                 }
645             }
646 
647             request.setDone();
648         }
649 
650         return nHandles;
651     }
652 
653     private void notifyIdleSessions(long currentTime) {
654         // process idle sessions
655         if (currentTime - lastIdleCheckTime >= 1000) {
656             lastIdleCheckTime = currentTime;
657             AbstractIoSession.notifyIdleness(
658                     getListeners().getManagedSessions().values().iterator(),
659                     currentTime);
660         }
661     }
662 }