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.common;
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  import java.util.concurrent.ExecutorService;
34  import java.util.concurrent.LinkedBlockingQueue;
35  import java.util.concurrent.RejectedExecutionException;
36  import java.util.concurrent.ThreadPoolExecutor;
37  import java.util.concurrent.TimeUnit;
38  import java.util.concurrent.atomic.AtomicInteger;
39  
40  import org.apache.mina.util.NamePreservingRunnable;
41  
42  /**
43   * {@link IoAcceptor} for datagram transport (UDP/IP).
44   *
45   * @author The Apache MINA Project (dev@mina.apache.org)
46   * @version $Rev: 605069 $, $Date: 2007-12-17 19:47:03 -0700 (Mon, 17 Dec 2007) $
47   */
48  public abstract class AbstractPollingConnectionlessIoAcceptor<T extends AbstractIoSession, H>
49          extends AbstractIoAcceptor {
50  
51      private static final IoSessionRecycler DEFAULT_RECYCLER = new ExpiringSessionRecycler();
52  
53      private static final AtomicInteger id = new AtomicInteger();
54  
55      private final Object lock = new Object();
56      private final Executor executor;
57      private final boolean createdExecutor;
58      private final String threadName;
59      private final IoProcessor<T> processor = new ConnectionlessAcceptorProcessor();
60      private final Queue<AcceptorOperationFuture> registerQueue = 
61          new ConcurrentLinkedQueue<AcceptorOperationFuture>();
62      private final Queue<AcceptorOperationFuture> cancelQueue = 
63          new ConcurrentLinkedQueue<AcceptorOperationFuture>();
64      private final Queue<T> flushingSessions = new ConcurrentLinkedQueue<T>();
65      private final Map<SocketAddress, H> boundHandles =
66          Collections.synchronizedMap(new HashMap<SocketAddress, H>());
67  
68      private IoSessionRecycler sessionRecycler = DEFAULT_RECYCLER;
69  
70      private final ServiceOperationFuture disposalFuture =
71          new ServiceOperationFuture();
72      private volatile boolean selectable;
73      private Worker worker;
74      private long lastIdleCheckTime;
75  
76      /**
77       * Creates a new instance.
78       */
79      protected AbstractPollingConnectionlessIoAcceptor(IoSessionConfig sessionConfig) {
80          this(sessionConfig, null);
81      }
82  
83      /**
84       * Creates a new instance.
85       */
86      protected AbstractPollingConnectionlessIoAcceptor(IoSessionConfig sessionConfig, Executor executor) {
87          super(sessionConfig);
88  
89          threadName = getClass().getSimpleName() + '-' + id.incrementAndGet();
90          
91          if (executor == null) {
92              this.executor = new ThreadPoolExecutor(
93                      1, 1, 1L, TimeUnit.SECONDS,
94                      new LinkedBlockingQueue<Runnable>());
95              this.createdExecutor = true;
96          } else {
97              this.executor = executor;
98              this.createdExecutor = false;
99          }
100         
101         try {
102             init();
103             selectable = true;
104         } catch (RuntimeException e){
105             throw e;
106         } catch (Exception e) {
107             throw new RuntimeIoException("Failed to initialize.", e);
108         } finally {
109             if (!selectable) {
110                 try {
111                     destroy();
112                 } catch (Exception e) {
113                     ExceptionMonitor.getInstance().exceptionCaught(e);
114                 }
115             }
116         }
117     }
118 
119     protected abstract void init() throws Exception;
120     protected abstract void destroy() throws Exception;
121     protected abstract boolean select(int timeout) throws Exception;
122     protected abstract void wakeup();
123     protected abstract Iterator<H> selectedHandles();
124     protected abstract H open(SocketAddress localAddress) throws Exception;
125     protected abstract void close(H handle) throws Exception;
126     protected abstract SocketAddress localAddress(H handle) throws Exception;
127     protected abstract boolean isReadable(H handle);
128     protected abstract boolean isWritable(H handle);
129     protected abstract SocketAddress receive(H handle, IoBuffer buffer) throws Exception;
130     protected abstract int send(T session, IoBuffer buffer, SocketAddress remoteAddress) throws Exception;
131     protected abstract T newSession(IoProcessor<T> processor, H handle, SocketAddress remoteAddress) throws Exception;
132     protected abstract void setInterestedInWrite(T session, boolean interested) throws Exception;
133 
134     @Override
135     protected IoFuture dispose0() throws Exception {
136         unbind();
137         if (!disposalFuture.isDone()) {
138             try {
139                 startupWorker();
140                 wakeup();
141             } catch (RejectedExecutionException e) {
142                 if (createdExecutor) {
143                     // Ignore.
144                 } else {
145                     throw e;
146                 }
147             }
148         }
149         return disposalFuture;
150     }
151 
152     @Override
153     protected final Set<SocketAddress> bind0(
154             List<? extends SocketAddress> localAddresses) throws Exception {
155         AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);
156 
157         registerQueue.add(request);
158         startupWorker();
159         wakeup();
160 
161         request.awaitUninterruptibly();
162 
163         if (request.getException() != null) {
164             throw request.getException();
165         }
166 
167         Set<SocketAddress> newLocalAddresses = new HashSet<SocketAddress>();
168         for (H handle: boundHandles.values()) {
169             newLocalAddresses.add(localAddress(handle));
170         }
171         return newLocalAddresses;
172     }
173 
174     @Override
175     protected final void unbind0(
176             List<? extends SocketAddress> localAddresses) throws Exception {
177         AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses);
178 
179         cancelQueue.add(request);
180         startupWorker();
181         wakeup();
182 
183         request.awaitUninterruptibly();
184 
185         if (request.getException() != null) {
186             throw request.getException();
187         }
188     }
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 updateTrafficMask(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 (worker == null) {
309                 worker = new Worker();
310                 executor.execute(
311                         new NamePreservingRunnable(worker, threadName));
312             }
313         }
314     }
315 
316     private boolean scheduleFlush(T session) {
317         if (session.setScheduledForFlush(true)) {
318             flushingSessions.add(session);
319             return true;
320         } else {
321             return false;
322         }
323     }
324 
325     private class Worker implements Runnable {
326         public void run() {
327             int nHandles = 0;
328             lastIdleCheckTime = System.currentTimeMillis();
329 
330             while (selectable) {
331                 try {
332                     boolean selected = select(1000);
333 
334                     nHandles += registerHandles();
335 
336                     if (selected) {
337                         processReadySessions(selectedHandles());
338                     }
339 
340                     flushSessions();
341                     nHandles -= unregisterHandles();
342 
343                     notifyIdleSessions();
344 
345                     if (nHandles == 0) {
346                         synchronized (lock) {
347                             if (registerQueue.isEmpty() && cancelQueue.isEmpty()) {
348                                 worker = null;
349                                 break;
350                             }
351                         }
352                     }
353                 } catch (Exception e) {
354                     ExceptionMonitor.getInstance().exceptionCaught(e);
355 
356                     try {
357                         Thread.sleep(1000);
358                     } catch (InterruptedException e1) {
359                     }
360                 }
361             }
362             
363             if (selectable && isDisposing()) {
364                 selectable = false;
365                 try {
366                     destroy();
367                 } catch (Exception e) {
368                     ExceptionMonitor.getInstance().exceptionCaught(e);
369                 } finally {
370                     disposalFuture.setValue(true);
371                     if (createdExecutor) {
372                         ((ExecutorService) executor).shutdown();
373                     }
374                 }
375             }
376         }
377     }
378 
379     @SuppressWarnings("unchecked")
380     private void processReadySessions(Iterator<H> handles) {
381         while (handles.hasNext()) {
382             H h = handles.next();
383             handles.remove();
384             try {
385                 if (isReadable(h)) {
386                     readHandle(h);
387                 }
388 
389                 if (isWritable(h)) {
390                     for (IoSession session : getManagedSessions()) {
391                         scheduleFlush((T) session);
392                     }
393                 }
394             } catch (Throwable t) {
395                 ExceptionMonitor.getInstance().exceptionCaught(t);
396             }
397         }
398     }
399 
400     private void readHandle(H handle) throws Exception {
401         IoBuffer readBuf = IoBuffer.allocate(
402                 getSessionConfig().getReadBufferSize());
403 
404         SocketAddress remoteAddress = receive(handle, readBuf);
405         if (remoteAddress != null) {
406             IoSession session = newSessionWithoutLock(
407                     remoteAddress, localAddress(handle));
408 
409             readBuf.flip();
410 
411             IoBuffer newBuf = IoBuffer.allocate(readBuf.limit());
412             newBuf.put(readBuf);
413             newBuf.flip();
414 
415             session.getFilterChain().fireMessageReceived(newBuf);
416         }
417     }
418 
419     private void flushSessions() {
420         for (; ;) {
421             T session = flushingSessions.poll();
422             if (session == null) {
423                 break;
424             }
425 
426             session.setScheduledForFlush(false);
427 
428             try {
429                 boolean flushedAll = flush(session);
430                 if (flushedAll && !session.getWriteRequestQueue().isEmpty(session) &&
431                     !session.isScheduledForFlush()) {
432                     scheduleFlush(session);
433                 }
434             } catch (Exception e) {
435                 session.getFilterChain().fireExceptionCaught(e);
436             }
437         }
438     }
439 
440     private boolean flush(T session) throws Exception {
441         // Clear OP_WRITE
442         setInterestedInWrite(session, false);
443         
444         WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue();
445 
446         int maxWrittenBytes =
447             session.getConfig().getMaxReadBufferSize() +
448             (session.getConfig().getMaxReadBufferSize() >>> 1);
449 
450         int writtenBytes = 0;
451         for (; ;) {
452             WriteRequest req = session.getCurrentWriteRequest();
453             if (req == null) {
454                 req = writeRequestQueue.poll(session);
455                 if (req == null) {
456                     break;
457                 }
458                 session.setCurrentWriteRequest(req);
459             }
460 
461             IoBuffer buf = (IoBuffer) req.getMessage();
462             if (buf.remaining() == 0) {
463                 // Clear and fire event
464                 session.setCurrentWriteRequest(null);
465                 buf.reset();
466                 session.getFilterChain().fireMessageSent(req);
467                 continue;
468             }
469 
470             SocketAddress destination = req.getDestination();
471             if (destination == null) {
472                 destination = session.getRemoteAddress();
473             }
474 
475             int localWrittenBytes = send(session, buf, destination);
476             if (localWrittenBytes == 0 || writtenBytes >= maxWrittenBytes) {
477                 // Kernel buffer is full or wrote too much
478                 setInterestedInWrite(session, true);
479                 return false;
480             } else {
481                 setInterestedInWrite(session, false);
482 
483                 // Clear and fire event
484                 session.setCurrentWriteRequest(null);
485                 writtenBytes += localWrittenBytes;
486                 buf.reset();
487                 session.getFilterChain().fireMessageSent(req);
488             }
489         }
490 
491         return true;
492     }
493 
494     private int registerHandles() {
495         for (;;) {
496             AcceptorOperationFuture req = registerQueue.poll();
497             if (req == null) {
498                 break;
499             }
500 
501             Map<SocketAddress, H> newHandles = new HashMap<SocketAddress, H>();
502             List<SocketAddress> localAddresses = req.getLocalAddresses();
503             try {
504                 for (SocketAddress a: localAddresses) {
505                     H handle = open(a);
506                     newHandles.put(localAddress(handle), handle);
507                 }
508                 boundHandles.putAll(newHandles);
509                 
510                 getListeners().fireServiceActivated();
511                 req.setDone();
512                 return newHandles.size();
513             } catch (Exception e) {
514                 req.setException(e);
515             } finally {
516                 // Roll back if failed to bind all addresses.
517                 if (req.getException() != null) {
518                     for (H handle: newHandles.values()) {
519                         try {
520                             close(handle);
521                         } catch (Exception e) {
522                             ExceptionMonitor.getInstance().exceptionCaught(e);
523                         }
524                     }
525                     wakeup();
526                 }
527             }
528         }
529         
530         return 0;
531     }
532 
533     private int unregisterHandles() {
534         int nHandles = 0;
535         for (;;) {
536             AcceptorOperationFuture request = cancelQueue.poll();
537             if (request == null) {
538                 break;
539             }
540 
541             // close the channels
542             for (SocketAddress a: request.getLocalAddresses()) {
543                 H handle = boundHandles.remove(a);
544                 if (handle == null) {
545                     continue;
546                 }
547 
548                 try {
549                     close(handle);
550                     wakeup(); // wake up again to trigger thread death
551                 } catch (Throwable e) {
552                     ExceptionMonitor.getInstance().exceptionCaught(e);
553                 } finally {
554                     nHandles ++;
555                 }
556             }
557             
558             request.setDone();
559         }
560         
561         return nHandles;
562     }
563 
564     private void notifyIdleSessions() {
565         // process idle sessions
566         long currentTime = System.currentTimeMillis();
567         if (currentTime - lastIdleCheckTime >= 1000) {
568             lastIdleCheckTime = currentTime;
569             IdleStatusChecker.notifyIdleness(
570                     getListeners().getManagedSessions().iterator(),
571                     currentTime);
572         }
573     }
574 }