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