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.List;
27  import java.util.Map;
28  import java.util.concurrent.Executor;
29  
30  import org.apache.mina.core.RuntimeIoException;
31  import org.apache.mina.core.buffer.IoBuffer;
32  import org.apache.mina.core.file.FileRegion;
33  import org.apache.mina.core.polling.AbstractPollingIoProcessor;
34  import org.apache.mina.util.CircularQueue;
35  import org.apache.tomcat.jni.Poll;
36  import org.apache.tomcat.jni.Pool;
37  import org.apache.tomcat.jni.Socket;
38  import org.apache.tomcat.jni.Status;
39  
40  /**
41   * The class in charge of processing socket level IO events for the {@link AprSocketConnector}
42   *
43   * @author The Apache MINA Project (dev@mina.apache.org)
44   * @version $Rev: 708221 $, $Date: 2008-10-27 16:31:36 +0100 (Mon, 27 Oct 2008) $
45   */
46  public final class AprIoProcessor extends AbstractPollingIoProcessor<AprSession> {
47      private static final int POLLSET_SIZE = 1024;
48  
49      private final Map<Long, AprSession> allSessions =
50          new HashMap<Long, AprSession>(POLLSET_SIZE);
51  
52      private final Object wakeupLock = new Object();
53      private final long wakeupSocket;
54      private volatile boolean toBeWakenUp;
55  
56      private final long pool;
57      private final long bufferPool; // memory pool
58      private final long pollset; // socket poller
59      private final long[] polledSockets = new long[POLLSET_SIZE << 1];
60      private final List<AprSession> polledSessions =
61          new CircularQueue<AprSession>(POLLSET_SIZE);
62  
63      public AprIoProcessor(Executor executor) {
64          super(executor);
65  
66          // initialize a memory pool for APR functions
67          pool = Pool.create(AprLibrary.getInstance().getRootPool());
68          bufferPool = Pool.create(AprLibrary.getInstance().getRootPool());
69  
70          try {
71              wakeupSocket = Socket.create(
72                      Socket.APR_INET, Socket.SOCK_DGRAM, Socket.APR_PROTO_UDP, pool);
73          } catch (RuntimeException e) {
74              throw e;
75          } catch (Error e) {
76              throw e;
77          } catch (Exception e) {
78              throw new RuntimeIoException("Failed to create a wakeup socket.", e);
79          }
80  
81          boolean success = false;
82          long newPollset;
83          try {
84              newPollset = Poll.create(
85                      POLLSET_SIZE,
86                      pool,
87                      Poll.APR_POLLSET_THREADSAFE,
88                      Long.MAX_VALUE);
89  
90              if (newPollset == 0) {
91                  newPollset = Poll.create(
92                          62,
93                          pool,
94                          Poll.APR_POLLSET_THREADSAFE,
95                          Long.MAX_VALUE);
96              }
97  
98              pollset = newPollset;
99              if (pollset < 0) {
100                 if (Status.APR_STATUS_IS_ENOTIMPL(- (int) pollset)) {
101                     throw new RuntimeIoException(
102                             "Thread-safe pollset is not supported in this platform.");
103                 }
104             }
105             success = true;
106         } catch (RuntimeException e) {
107             throw e;
108         } catch (Error e) {
109             throw e;
110         } catch (Exception e) {
111             throw new RuntimeIoException("Failed to create a pollset.", e);
112         } finally {
113             if (!success) {
114                 dispose();
115             }
116         }
117     }
118 
119     @Override
120     protected void dispose0() {
121         Poll.destroy(pollset);
122         Socket.close(wakeupSocket);
123         Pool.destroy(bufferPool);
124         Pool.destroy(pool);
125     }
126 
127     @Override
128     protected int select() throws Exception {
129     	return select(Integer.MAX_VALUE);
130     }
131 
132  	@Override
133     protected int select(int timeout) throws Exception {
134         int rv = Poll.poll(pollset, 1000 * timeout, polledSockets, false);
135         if (rv <= 0) {
136             if (rv != -120001) {
137                 throwException(rv);
138             }
139 
140             rv = Poll.maintain(pollset, polledSockets, true);
141             if (rv > 0) {
142                 for (int i = 0; i < rv; i ++) {
143                     long socket = polledSockets[i];
144                     AprSession session = allSessions.get(socket);
145                     if (session == null) {
146                         continue;
147                     }
148 
149                     int flag = (session.isInterestedInRead()? Poll.APR_POLLIN : 0) |
150                                (session.isInterestedInWrite()? Poll.APR_POLLOUT : 0);
151 
152                     Poll.add(pollset, socket, flag);
153                 }
154             } else if (rv < 0) {
155                 throwException(rv);
156             }
157 
158             return 0;
159         } else {
160             rv <<= 1;
161             if (!polledSessions.isEmpty()) {
162                 polledSessions.clear();
163             }
164             for (int i = 0; i < rv; i ++) {
165                 long flag = polledSockets[i];
166                 long socket = polledSockets[++i];
167                 if (socket == wakeupSocket) {
168                     synchronized (wakeupLock) {
169                         Poll.remove(pollset, wakeupSocket);
170                         toBeWakenUp = false;
171                     }
172                     continue;
173                 }
174                 AprSession session = allSessions.get(socket);
175                 if (session == null) {
176                     continue;
177                 }
178 
179                 session.setReadable((flag & Poll.APR_POLLIN) != 0);
180                 session.setWritable((flag & Poll.APR_POLLOUT) != 0);
181 
182                 polledSessions.add(session);
183             }
184 
185             return polledSessions.size();
186         }
187     }
188 
189     @Override
190     protected boolean isSelectorEmpty() {
191         return allSessions.isEmpty();
192     }
193 
194     @Override
195     protected void wakeup() {
196         if (toBeWakenUp) {
197             return;
198         }
199 
200         // Add a dummy socket to the pollset.
201         synchronized (wakeupLock) {
202             toBeWakenUp = true;
203             Poll.add(pollset, wakeupSocket, Poll.APR_POLLOUT);
204         }
205     }
206 
207     @Override
208     protected Iterator<AprSession> allSessions() {
209         return allSessions.values().iterator();
210     }
211 
212     @Override
213     protected Iterator<AprSession> selectedSessions() {
214         return polledSessions.iterator();
215     }
216 
217     @Override
218     protected void init(AprSession session) throws Exception {
219         long s = session.getDescriptor();
220         Socket.optSet(s, Socket.APR_SO_NONBLOCK, 1);
221         Socket.timeoutSet(s, 0);
222 
223         int rv = Poll.add(pollset, s, Poll.APR_POLLIN);
224         if (rv != Status.APR_SUCCESS) {
225             throwException(rv);
226         }
227 
228         session.setInterestedInRead(true);
229         allSessions.put(s, session);
230     }
231 
232     @Override
233     protected void destroy(AprSession session) throws Exception {
234         if (allSessions.remove(session.getDescriptor()) == null) {
235             // Already destroyed.
236             return;
237         }
238 
239         int ret = Poll.remove(pollset, session.getDescriptor());
240         try {
241             if (ret != Status.APR_SUCCESS) {
242                 throwException(ret);
243             }
244         } finally {
245             ret = Socket.close(session.getDescriptor());
246             
247         	// destroying the session because it won't be reused 
248             // after this point
249             Socket.destroy(session.getDescriptor());
250             session.setDescriptor(0);
251             
252             if (ret != Status.APR_SUCCESS) {
253                 throwException(ret);
254             }
255         }
256     }
257 
258     @Override
259     protected SessionState state(AprSession session) {
260         long socket = session.getDescriptor();
261         if (socket != 0) {
262             return SessionState.OPEN;
263         } else if (allSessions.get(socket) != null) {
264             return SessionState.PREPARING; // will occur ?
265         } else {
266             return SessionState.CLOSED;
267         }
268     }
269 
270     @Override
271     protected boolean isReadable(AprSession session) {
272         return session.isReadable();
273     }
274 
275     @Override
276     protected boolean isWritable(AprSession session) {
277         return session.isWritable();
278     }
279 
280     @Override
281     protected boolean isInterestedInRead(AprSession session) {
282         return session.isInterestedInRead();
283     }
284 
285     @Override
286     protected boolean isInterestedInWrite(AprSession session) {
287         return session.isInterestedInWrite();
288     }
289 
290     @Override
291     protected void setInterestedInRead(AprSession session, boolean value) throws Exception {
292         if (session.isInterestedInRead() == value) {
293             return;
294         }
295 
296         int rv = Poll.remove(pollset, session.getDescriptor());
297         if (rv != Status.APR_SUCCESS) {
298             throwException(rv);
299         }
300 
301         int flags = (value ? Poll.APR_POLLIN : 0)
302                 | (session.isInterestedInWrite() ? Poll.APR_POLLOUT : 0);
303 
304         rv = Poll.add(pollset, session.getDescriptor(), flags);
305         if (rv == Status.APR_SUCCESS) {
306             session.setInterestedInRead(value);
307         } else {
308             throwException(rv);
309         }
310     }
311 
312     @Override
313     protected void setInterestedInWrite(AprSession session, boolean value) throws Exception {
314         if (session.isInterestedInWrite() == value) {
315             return;
316         }
317 
318         int rv = Poll.remove(pollset, session.getDescriptor());
319         if (rv != Status.APR_SUCCESS) {
320             throwException(rv);
321         }
322 
323         int flags = (session.isInterestedInRead() ? Poll.APR_POLLIN : 0)
324                 | (value ? Poll.APR_POLLOUT : 0);
325 
326         rv = Poll.add(pollset, session.getDescriptor(), flags);
327         if (rv == Status.APR_SUCCESS) {
328             session.setInterestedInWrite(value);
329         } else {
330             throwException(rv);
331         }
332     }
333 
334     @Override
335     protected int read(AprSession session, IoBuffer buffer) throws Exception {
336         int bytes;
337         int capacity = buffer.remaining();
338         // Using Socket.recv() directly causes memory leak. :-(
339         ByteBuffer b = Pool.alloc(bufferPool, capacity);
340         try {
341             bytes = Socket.recvb(
342                     session.getDescriptor(), b, 0, capacity);
343             if (bytes > 0) {
344                 b.position(0);
345                 b.limit(bytes);
346                 buffer.put(b);
347             } else if (bytes < 0) {
348                 if (Status.APR_STATUS_IS_EOF(-bytes)) {
349                     bytes = -1;
350                 } else if (Status.APR_STATUS_IS_EAGAIN(-bytes)) {
351                     bytes = 0;
352                 } else {
353                     throwException(bytes);
354                 }
355             }
356         } finally {
357             Pool.clear(bufferPool);
358         }
359         return bytes;
360     }
361 
362     @Override
363     protected int write(AprSession session, IoBuffer buf, int length) throws Exception {
364         int writtenBytes;
365         if (buf.isDirect()) {
366             writtenBytes = Socket.sendb(
367                     session.getDescriptor(), buf.buf(), buf.position(), length);
368         } else {
369             writtenBytes = Socket.send(
370                     session.getDescriptor(), buf.array(), buf.position(), length);
371             if (writtenBytes > 0) {
372                 buf.skip(writtenBytes);
373             }
374         }
375 
376         if (writtenBytes < 0) {
377             if (Status.APR_STATUS_IS_EAGAIN(-writtenBytes)) {
378                 writtenBytes = 0;
379             } else if (Status.APR_STATUS_IS_EOF(-writtenBytes)) {
380                 writtenBytes = 0;
381             } else {
382                 throwException(writtenBytes);
383             }
384         }
385         return writtenBytes;
386     }
387 
388     @Override
389     protected int transferFile(AprSession session, FileRegion region, int length)
390             throws Exception {
391         throw new UnsupportedOperationException();
392     }
393 
394     private void throwException(int code) throws IOException {
395         throw new IOException(
396                 org.apache.tomcat.jni.Error.strerror(-code) +
397                 " (code: " + code + ")");
398     }
399 }