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