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.socket.apr;
21  
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Queue;
28  import java.util.concurrent.ConcurrentLinkedQueue;
29  import java.util.concurrent.Executor;
30  
31  import org.apache.mina.core.RuntimeIoException;
32  import org.apache.mina.core.buffer.IoBuffer;
33  import org.apache.mina.core.file.FileRegion;
34  import org.apache.mina.core.polling.AbstractPollingIoProcessor;
35  import org.apache.mina.core.session.SessionState;
36  import org.apache.tomcat.jni.File;
37  import org.apache.tomcat.jni.Poll;
38  import org.apache.tomcat.jni.Pool;
39  import org.apache.tomcat.jni.Socket;
40  import org.apache.tomcat.jni.Status;
41  
42  /**
43   * The class in charge of processing socket level IO events for the
44   * {@link AprSocketConnector}
45   *
46   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
47   */
48  public final class AprIoProcessor extends AbstractPollingIoProcessor<AprSession> {
49      private static final int POLLSET_SIZE = 1024;
50  
51      private final Map<Long, AprSession> allSessions = new HashMap<Long, AprSession>(POLLSET_SIZE);
52  
53      private final Object wakeupLock = new Object();
54      private final long wakeupSocket;
55      private volatile boolean toBeWakenUp;
56  
57      private final long pool;
58      private final long bufferPool; // memory pool
59      private final long pollset; // socket poller
60      private final long[] polledSockets = new long[POLLSET_SIZE << 1];
61      private final Queue<AprSession> polledSessions = new ConcurrentLinkedQueue<AprSession>();
62  
63      /**
64       * Create a new instance of {@link AprIoProcessor} with a given Exector for
65       * handling I/Os events.
66       *
67       * @param executor
68       *            the {@link Executor} for handling I/O events
69       */
70      public AprIoProcessor(Executor executor) {
71          super(executor);
72  
73          // initialize a memory pool for APR functions
74          pool = Pool.create(AprLibrary.getInstance().getRootPool());
75          bufferPool = Pool.create(AprLibrary.getInstance().getRootPool());
76  
77          try {
78              wakeupSocket = Socket.create(Socket.APR_INET, Socket.SOCK_DGRAM, Socket.APR_PROTO_UDP, pool);
79          } catch (RuntimeException e) {
80              throw e;
81          } catch (Error e) {
82              throw e;
83          } catch (Exception e) {
84              throw new RuntimeIoException("Failed to create a wakeup socket.", e);
85          }
86  
87          boolean success = false;
88          long newPollset;
89          try {
90              newPollset = Poll.create(POLLSET_SIZE, pool, Poll.APR_POLLSET_THREADSAFE, Long.MAX_VALUE);
91  
92              if (newPollset == 0) {
93                  newPollset = Poll.create(62, pool, Poll.APR_POLLSET_THREADSAFE, Long.MAX_VALUE);
94              }
95  
96              pollset = newPollset;
97              if (pollset < 0) {
98                  if (Status.APR_STATUS_IS_ENOTIMPL(-(int) pollset)) {
99                      throw new RuntimeIoException("Thread-safe pollset is not supported in this platform.");
100                 }
101             }
102             success = true;
103         } catch (RuntimeException e) {
104             throw e;
105         } catch (Error e) {
106             throw e;
107         } catch (Exception e) {
108             throw new RuntimeIoException("Failed to create a pollset.", e);
109         } finally {
110             if (!success) {
111                 dispose();
112             }
113         }
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119     @Override
120     protected void doDispose() {
121         Poll.destroy(pollset);
122         Socket.close(wakeupSocket);
123         Pool.destroy(bufferPool);
124         Pool.destroy(pool);
125     }
126 
127     /**
128      * {@inheritDoc}
129      */
130     @Override
131     protected int select() throws Exception {
132         return select(Integer.MAX_VALUE);
133     }
134 
135     /**
136      * {@inheritDoc}
137      */
138     @Override
139     protected int select(long timeout) throws Exception {
140         int rv = Poll.poll(pollset, 1000 * timeout, polledSockets, false);
141         if (rv <= 0) {
142             if (rv != -120001) {
143                 throwException(rv);
144             }
145 
146             rv = Poll.maintain(pollset, polledSockets, true);
147             if (rv > 0) {
148                 for (int i = 0; i < rv; i++) {
149                     long socket = polledSockets[i];
150                     AprSession session = allSessions.get(socket);
151                     if (session == null) {
152                         continue;
153                     }
154 
155                     int flag = (session.isInterestedInRead() ? Poll.APR_POLLIN : 0)
156                             | (session.isInterestedInWrite() ? Poll.APR_POLLOUT : 0);
157 
158                     Poll.add(pollset, socket, flag);
159                 }
160             } else if (rv < 0) {
161                 throwException(rv);
162             }
163 
164             return 0;
165         } else {
166             rv <<= 1;
167             if (!polledSessions.isEmpty()) {
168                 polledSessions.clear();
169             }
170             for (int i = 0; i < rv; i++) {
171                 long flag = polledSockets[i];
172                 long socket = polledSockets[++i];
173                 if (socket == wakeupSocket) {
174                     synchronized (wakeupLock) {
175                         Poll.remove(pollset, wakeupSocket);
176                         toBeWakenUp = false;
177                         wakeupCalled.set(true);
178                     }
179                     continue;
180                 }
181                 AprSession session = allSessions.get(socket);
182                 if (session == null) {
183                     continue;
184                 }
185 
186                 session.setReadable((flag & Poll.APR_POLLIN) != 0);
187                 session.setWritable((flag & Poll.APR_POLLOUT) != 0);
188 
189                 polledSessions.add(session);
190             }
191 
192             return polledSessions.size();
193         }
194     }
195 
196     /**
197      * {@inheritDoc}
198      */
199     @Override
200     protected boolean isSelectorEmpty() {
201         return allSessions.isEmpty();
202     }
203 
204     /**
205      * {@inheritDoc}
206      */
207     @Override
208     protected void wakeup() {
209         if (toBeWakenUp) {
210             return;
211         }
212 
213         // Add a dummy socket to the pollset.
214         synchronized (wakeupLock) {
215             toBeWakenUp = true;
216             Poll.add(pollset, wakeupSocket, Poll.APR_POLLOUT);
217         }
218     }
219 
220     /**
221      * {@inheritDoc}
222      */
223     @Override
224     protected Iterator<AprSession> allSessions() {
225         return allSessions.values().iterator();
226     }
227 
228     /**
229      * {@inheritDoc}
230      */
231     @Override
232     protected Iterator<AprSession> selectedSessions() {
233         return polledSessions.iterator();
234     }
235 
236     @Override
237     protected void init(AprSession session) throws Exception {
238         long s = session.getDescriptor();
239         Socket.optSet(s, Socket.APR_SO_NONBLOCK, 1);
240         Socket.timeoutSet(s, 0);
241 
242         int rv = Poll.add(pollset, s, Poll.APR_POLLIN);
243         if (rv != Status.APR_SUCCESS) {
244             throwException(rv);
245         }
246 
247         session.setInterestedInRead(true);
248         allSessions.put(s, session);
249     }
250 
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     protected void destroy(AprSession session) throws Exception {
256         if (allSessions.remove(session.getDescriptor()) == null) {
257             // Already destroyed.
258             return;
259         }
260 
261         int ret = Poll.remove(pollset, session.getDescriptor());
262         try {
263             if (ret != Status.APR_SUCCESS) {
264                 throwException(ret);
265             }
266         } finally {
267             ret = Socket.close(session.getDescriptor());
268 
269             // destroying the session because it won't be reused
270             // after this point
271             Socket.destroy(session.getDescriptor());
272             session.setDescriptor(0);
273 
274             if (ret != Status.APR_SUCCESS) {
275                 throwException(ret);
276             }
277         }
278     }
279 
280     /**
281      * {@inheritDoc}
282      */
283     @Override
284     protected SessionState getState(AprSession session) {
285         long socket = session.getDescriptor();
286 
287         if (socket != 0) {
288             return SessionState.OPENED;
289         } else if (allSessions.get(socket) != null) {
290             return SessionState.OPENING; // will occur ?
291         } else {
292             return SessionState.CLOSING;
293         }
294     }
295 
296     /**
297      * {@inheritDoc}
298      */
299     @Override
300     protected boolean isReadable(AprSession session) {
301         return session.isReadable();
302     }
303 
304     /**
305      * {@inheritDoc}
306      */
307     @Override
308     protected boolean isWritable(AprSession session) {
309         return session.isWritable();
310     }
311 
312     /**
313      * {@inheritDoc}
314      */
315     @Override
316     protected boolean isInterestedInRead(AprSession session) {
317         return session.isInterestedInRead();
318     }
319 
320     /**
321      * {@inheritDoc}
322      */
323     @Override
324     protected boolean isInterestedInWrite(AprSession session) {
325         return session.isInterestedInWrite();
326     }
327 
328     /**
329      * {@inheritDoc}
330      */
331     @Override
332     protected void setInterestedInRead(AprSession session, boolean isInterested) throws Exception {
333         if (session.isInterestedInRead() == isInterested) {
334             return;
335         }
336 
337         int rv = Poll.remove(pollset, session.getDescriptor());
338 
339         if (rv != Status.APR_SUCCESS) {
340             throwException(rv);
341         }
342 
343         int flags = (isInterested ? Poll.APR_POLLIN : 0) | (session.isInterestedInWrite() ? Poll.APR_POLLOUT : 0);
344 
345         rv = Poll.add(pollset, session.getDescriptor(), flags);
346 
347         if (rv == Status.APR_SUCCESS) {
348             session.setInterestedInRead(isInterested);
349         } else {
350             throwException(rv);
351         }
352     }
353 
354     /**
355      * {@inheritDoc}
356      */
357     @Override
358     protected void setInterestedInWrite(AprSession session, boolean isInterested) throws Exception {
359         if (session.isInterestedInWrite() == isInterested) {
360             return;
361         }
362 
363         int rv = Poll.remove(pollset, session.getDescriptor());
364 
365         if (rv != Status.APR_SUCCESS) {
366             throwException(rv);
367         }
368 
369         int flags = (session.isInterestedInRead() ? Poll.APR_POLLIN : 0) | (isInterested ? Poll.APR_POLLOUT : 0);
370 
371         rv = Poll.add(pollset, session.getDescriptor(), flags);
372 
373         if (rv == Status.APR_SUCCESS) {
374             session.setInterestedInWrite(isInterested);
375         } else {
376             throwException(rv);
377         }
378     }
379 
380     /**
381      * {@inheritDoc}
382      */
383     @Override
384     protected int read(AprSession session, IoBuffer buffer) throws Exception {
385         int bytes;
386         int capacity = buffer.remaining();
387         // Using Socket.recv() directly causes memory leak. :-(
388         ByteBuffer b = Pool.alloc(bufferPool, capacity);
389 
390         try {
391             bytes = Socket.recvb(session.getDescriptor(), b, 0, capacity);
392 
393             if (bytes > 0) {
394                 b.position(0);
395                 b.limit(bytes);
396                 buffer.put(b);
397             } else if (bytes < 0) {
398                 if (Status.APR_STATUS_IS_EOF(-bytes)) {
399                     bytes = -1;
400                 } else if (Status.APR_STATUS_IS_EAGAIN(-bytes)) {
401                     bytes = 0;
402                 } else {
403                     throwException(bytes);
404                 }
405             }
406         } finally {
407             Pool.clear(bufferPool);
408         }
409 
410         return bytes;
411     }
412 
413     /**
414      * {@inheritDoc}
415      */
416     @Override
417     protected int write(AprSession session, IoBuffer buf, int length) throws Exception {
418         int writtenBytes;
419         if (buf.isDirect()) {
420             writtenBytes = Socket.sendb(session.getDescriptor(), buf.buf(), buf.position(), length);
421         } else {
422             writtenBytes = Socket.send(session.getDescriptor(), buf.array(), buf.position(), length);
423             if (writtenBytes > 0) {
424                 buf.skip(writtenBytes);
425             }
426         }
427 
428         if (writtenBytes < 0) {
429             if (Status.APR_STATUS_IS_EAGAIN(-writtenBytes)) {
430                 writtenBytes = 0;
431             } else if (Status.APR_STATUS_IS_EOF(-writtenBytes)) {
432                 writtenBytes = 0;
433             } else {
434                 throwException(writtenBytes);
435             }
436         }
437         return writtenBytes;
438     }
439 
440     /**
441      * {@inheritDoc}
442      */
443     @Override
444     protected int transferFile(AprSession session, FileRegion region, int length) throws Exception {
445         if (region.getFilename() == null) {
446             throw new UnsupportedOperationException();
447         }
448 
449         long fd = File.open(region.getFilename(),
450                             File.APR_FOPEN_READ
451                                 | File.APR_FOPEN_SENDFILE_ENABLED
452                                 | File.APR_FOPEN_BINARY,
453                             0,
454                             Socket.pool(session.getDescriptor()));
455         long numWritten = Socket.sendfilen(session.getDescriptor(), fd, region.getPosition(), length, 0);
456         File.close(fd);
457 
458         if (numWritten < 0) {
459             if (numWritten == -Status.EAGAIN) {
460                 return 0;
461             }
462             throw new IOException(org.apache.tomcat.jni.Error.strerror((int) -numWritten) + " (code: " + numWritten + ")");
463         }
464         return (int) numWritten;
465     }
466 
467     private void throwException(int code) throws IOException {
468         throw new IOException(org.apache.tomcat.jni.Error.strerror(-code) + " (code: " + code + ")");
469     }
470 
471     /**
472      * {@inheritDoc}
473      */
474     @Override
475     protected void registerNewSelector() {
476         // Do nothing
477     }
478 
479     /**
480      * {@inheritDoc}
481      */
482     @Override
483     protected boolean isBrokenConnection() throws IOException {
484         // Here, we assume that this is the case.
485         return true;
486     }
487 }