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