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.transport.nio;
21  
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.ClosedChannelException;
25  import java.nio.channels.SelectableChannel;
26  import java.nio.channels.SelectionKey;
27  import java.nio.channels.Selector;
28  import java.util.Iterator;
29  import java.util.Queue;
30  import java.util.concurrent.ConcurrentLinkedQueue;
31  
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  /**
36   * This class holds a Selector and handle all the incoming events for the sessions registered on this selector.ALl the
37   * events will be processed by some dedicated thread, taken from a pool. It will loop forever, untill the instance is
38   * stopped.
39   * 
40   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
41   */
42  public class NioSelectorLoop implements SelectorLoop {
43      /** The logger for this class */
44      private static final Logger LOG = LoggerFactory.getLogger(NioSelectorLoop.class);
45  
46      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
47  
48      /** the selector managed by this class */
49      private Selector selector;
50  
51      /** Read buffer for all the incoming bytes (default to 64Kb) */
52      private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(64 * 1024);
53  
54      /** The queue containing the channels to register on the selector */
55      private final Queue<Registration> registrationQueue = new ConcurrentLinkedQueue<Registration>();
56  
57      /**
58       * Creates an instance of the SelectorLoop.
59       * 
60       * @param prefix
61       * @param index
62       */
63      public NioSelectorLoop(final String prefix) {
64          this(prefix, -1);
65      }
66  
67      /**
68       * Creates an instance of the SelectorLoop.
69       * 
70       * @param prefix
71       * @param index
72       */
73      public NioSelectorLoop(final String prefix, final int index) {
74          String workerName = "SelectorWorker " + prefix;
75  
76          if (index >= 0) {
77              workerName += "-" + index;
78          }
79  
80          SelectorWorker worker = new SelectorWorker(workerName);
81  
82          try {
83              if (IS_DEBUG) {
84                  LOG.debug("open a selector");
85              }
86  
87              selector = Selector.open();
88          } catch (final IOException ioe) {
89              LOG.error("Impossible to open a new NIO selector, O/S is out of file descriptor ?");
90              throw new IllegalStateException("Impossible to open a new NIO selector, O/S is out of file descriptor ?",
91                      ioe);
92          }
93  
94          if (IS_DEBUG) {
95              LOG.debug("starting worker thread");
96          }
97  
98          worker.start();
99  
100     }
101 
102     /**
103      * {@inheritDoc}
104      */
105     @Override
106     public void register(boolean accept, boolean connect, boolean read, boolean write, SelectorListener listener,
107             SelectableChannel channel, RegistrationCallback callback) {
108         if (IS_DEBUG) {
109             LOG.debug("registering : {} for accept : {}, connect: {}, read : {}, write : {}, channel : {}",
110                     new Object[] { listener, accept, connect, read, write, channel });
111         }
112 
113         int ops = 0;
114 
115         if (accept) {
116             ops |= SelectionKey.OP_ACCEPT;
117         }
118 
119         if (connect) {
120             ops |= SelectionKey.OP_CONNECT;
121         }
122 
123         if (read) {
124             ops |= SelectionKey.OP_READ;
125         }
126 
127         if (write) {
128             ops |= SelectionKey.OP_WRITE;
129         }
130 
131         // TODO : if it's the same selector/worker, we don't need to do that we could directly enqueue
132         registrationQueue.add(new Registration(ops, channel, listener, callback));
133 
134         // Now, wakeup the selector in order to let it update the selectionKey status
135         wakeup();
136     }
137 
138     /**
139      * {@inheritDoc}
140      */
141     @Override
142     public void modifyRegistration(boolean accept, boolean read, boolean write, final SelectorListener listener,
143             SelectableChannel channel, boolean wakeup) {
144         if (IS_DEBUG) {
145             LOG.debug("modifying registration : {} for accept : {}, read : {}, write : {}, channel : {}", new Object[] {
146                                     listener, accept, read, write, channel });
147         }
148 
149         final SelectionKey key = channel.keyFor(selector);
150 
151         if (key == null) {
152             LOG.error("Trying to modify the registration of a not registered channel");
153             return;
154         }
155 
156         int ops = 0;
157 
158         if (accept) {
159             ops |= SelectionKey.OP_ACCEPT;
160         }
161 
162         if (read) {
163             ops |= SelectionKey.OP_READ;
164         }
165 
166         if (write) {
167             ops |= SelectionKey.OP_WRITE;
168         }
169 
170         key.interestOps(ops);
171 
172         // we need to wakeup for the registration to be modified (TODO : not needed if we are in the worker thread)
173         if (wakeup) {
174             wakeup();
175         }
176     }
177 
178     /**
179      * {@inheritDoc}
180      */
181     @Override
182     public void unregister(final SelectorListener listener, final SelectableChannel channel) {
183         if (IS_DEBUG) {
184             LOG.debug("unregistering : {}", listener);
185         }
186 
187         final SelectionKey key = channel.keyFor(selector);
188 
189         if (key == null) {
190             LOG.error("Trying to modify the registration of a not registered channel");
191             return;
192         }
193         key.cancel();
194         key.attach(null);
195 
196         if (IS_DEBUG) {
197             LOG.debug("unregistering : {} done !", listener);
198         }
199     }
200 
201     /**
202      * The worker processing incoming session creation, session destruction requests, session write and reads. It will
203      * also bind new servers.
204      */
205     private class SelectorWorker extends Thread {
206 
207         public SelectorWorker(String name) {
208             super(name);
209             setDaemon(true);
210         }
211 
212         @Override
213         public void run() {
214 
215             for (;;) {
216                 try {
217                     if (IS_DEBUG) {
218                         LOG.debug("selecting...");
219                     }
220 
221                     final int readyCount = selector.select();
222 
223                     if (IS_DEBUG) {
224                         LOG.debug("... done selecting : {} events", readyCount);
225                     }
226 
227                     if (readyCount > 0) {
228                         final Iterator<SelectionKey> it = selector.selectedKeys().iterator();
229 
230                         while (it.hasNext()) {
231                             final SelectionKey key = it.next();
232                             final SelectorListener listener = (SelectorListener) key.attachment();
233                             int ops = key.readyOps();
234                             boolean isAcceptable = (ops & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
235                             boolean isConnectable = (ops & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
236                             boolean isReadable = (ops & SelectionKey.OP_READ) == SelectionKey.OP_READ;
237                             boolean isWritable = (ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
238                             listener.ready(isAcceptable, isConnectable, isReadable, isReadable ? readBuffer : null,
239                                     isWritable);
240                             // if you don't remove the event of the set, the selector will present you this event again
241                             // and again
242                             if (IS_DEBUG) {
243                                 LOG.debug("remove");
244                             }
245 
246                             it.remove();
247                         }
248                     }
249 
250                     // new registration
251                     while (!registrationQueue.isEmpty()) {
252                         final Registration reg = registrationQueue.poll();
253 
254                         try {
255                             SelectionKey selectionKey = reg.channel.register(selector, reg.ops, reg.listener);
256 
257                             if (reg.getCallback() != null) {
258                                 reg.getCallback().done(selectionKey);
259                             }
260                         } catch (final ClosedChannelException ex) {
261                             // dead session..
262                             LOG.error("socket is already dead", ex);
263                         }
264                     }
265                 } catch (final Exception e) {
266                     LOG.error("Unexpected exception : ", e);
267                 }
268             }
269         }
270     }
271 
272     @Override
273     public void wakeup() {
274         selector.wakeup();
275     }
276 
277     private static class Registration {
278 
279         public Registration(int ops, SelectableChannel channel, SelectorListener listener, RegistrationCallback callback) {
280             this.ops = ops;
281             this.channel = channel;
282             this.listener = listener;
283             this.callback = callback;
284         }
285 
286         private final int ops;
287 
288         private final SelectableChannel channel;
289 
290         private final SelectorListener listener;
291 
292         private final RegistrationCallback callback;
293 
294         public RegistrationCallback getCallback() {
295             return callback;
296         }
297 
298         @Override
299         public String toString() {
300             StringBuilder sb = new StringBuilder();
301 
302             sb.append("Registration : [");
303 
304             boolean hasOp = false;
305 
306             if ((ops & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
307                 sb.append("OP_READ");
308                 hasOp = true;
309             }
310 
311             if ((ops & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
312                 if (hasOp) {
313                     sb.append("|");
314                 }
315 
316                 sb.append("OP_WRITE");
317                 hasOp = true;
318             }
319 
320             if ((ops & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
321                 if (hasOp) {
322                     sb.append("|");
323                 }
324 
325                 sb.append("OP_ACCEPT");
326                 hasOp = true;
327             }
328 
329             if ((ops & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT) {
330                 if (hasOp) {
331                     sb.append("|");
332                 }
333 
334                 sb.append("OP_CONNECT");
335                 hasOp = true;
336             }
337 
338             if (channel != null) {
339                 sb.append(", ").append(channel);
340             }
341 
342             return sb.toString();
343         }
344     }
345 }