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.SelectableChannel;
25  import java.nio.channels.SocketChannel;
26  import java.util.Queue;
27  import java.util.concurrent.atomic.AtomicBoolean;
28  
29  import org.apache.mina.api.IoFuture;
30  import org.apache.mina.api.IoService;
31  import org.apache.mina.api.IoSession;
32  import org.apache.mina.service.idlechecker.IdleChecker;
33  import org.apache.mina.session.AbstractIoSession;
34  import org.apache.mina.session.DefaultWriteFuture;
35  import org.apache.mina.session.DefaultWriteQueue;
36  import org.apache.mina.session.DefaultWriteRequest;
37  import org.apache.mina.session.WriteRequest;
38  import org.apache.mina.util.AbstractIoFuture;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * Common ancestor for NIO based {@link IoSession} implmentation.
44   * 
45   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
46   */
47  public abstract class AbstractNioSession extends AbstractIoSession {
48      /** The logger for this class */
49      private static final Logger LOG = LoggerFactory.getLogger(AbstractNioSession.class);
50  
51      // A speedup for logs
52      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
53  
54      /** the NIO channel for this session */
55      protected final SelectableChannel channel;
56  
57      /** is this session registered for being polled for write ready events */
58      private final AtomicBoolean registeredForWrite = new AtomicBoolean();
59  
60      /** the queue of pending writes for the session, to be dequeued by the {@link SelectorLoop} */
61      private final Queue<WriteRequest> writeQueue = new DefaultWriteQueue();
62  
63      public AbstractNioSession(IoService service, SelectableChannel channel, IdleChecker idleChecker) {
64          super(service, idleChecker);
65          this.channel = channel;
66      }
67  
68      /**
69       * Writes the message immediately. If we can't write all the message, we will get back the number of written bytes.
70       * 
71       * @param message the message to write
72       * @return the number of written bytes
73       */
74      protected abstract int writeDirect(Object message);
75  
76      /**
77       * Copy the HeapBuffer into a DirectBuffer, if needed.
78       * 
79       * @param writeRequest The request containing the HeapBuffer
80       * @param createNew A flag to force the creation of a DirectBuffer
81       * @return A DirectBuffer
82       */
83      protected abstract ByteBuffer convertToDirectBuffer(WriteRequest writeRequest, boolean createNew);
84  
85      // ------------------------------------------------------------------------
86      // Close session management
87      // ------------------------------------------------------------------------
88  
89      /** we pre-allocate a close future for lock-less {@link #close(boolean)} */
90      private final IoFuture<Void> closeFuture = new AbstractIoFuture<Void>() {
91  
92          /**
93           * {@inheritDoc}
94           */
95          @Override
96          protected boolean cancelOwner(final boolean mayInterruptIfRunning) {
97              // we don't cancel close
98              return false;
99          }
100     };
101 
102     /**
103      * {@inheritDoc}
104      */
105     @Override
106     public IoFuture<Void> close(final boolean immediately) {
107         switch (state) {
108         case CREATED:
109             LOG.error("Session {} not opened", this);
110             throw new IllegalStateException("cannot close an not opened session");
111         case CONNECTED:
112             state = SessionState.CLOSING;
113             if (immediately) {
114                 channelClose();
115                 processSessionClosed();
116             } else {
117                 // flush this session the flushing code will close the session
118                 flushWriteQueue();
119             }
120             break;
121         case CLOSING:
122             // return the same future
123             LOG.warn("Already closing session {}", this);
124             break;
125         case CLOSED:
126             LOG.warn("Already closed session {}", this);
127             break;
128         default:
129             throw new IllegalStateException("not implemented session state : " + state);
130         }
131 
132         return closeFuture;
133     }
134 
135     /**
136      * Close the inner socket channel
137      */
138     protected abstract void channelClose();
139 
140     /**
141      * {@inheritDoc}
142      */
143     @Override
144     public WriteRequest enqueueWriteRequest(WriteRequest writeRequest) {
145         if (IS_DEBUG) {
146             LOG.debug("enqueueWriteRequest {}", writeRequest);
147         }
148 
149         if (isConnectedSecured()) {
150             // SSL/TLS : we have to encrypt the message
151             SslHelper sslHelper = getAttribute(SSL_HELPER, null);
152 
153             if (sslHelper == null) {
154                 throw new IllegalStateException();
155             }
156 
157             writeRequest = sslHelper.processWrite(this, writeRequest.getMessage(), writeQueue);
158         }
159 
160         /*synchronized (writeQueue)*/{
161             ByteBuffer message = (ByteBuffer) writeRequest.getMessage();
162 
163             if (writeQueue.isEmpty()) {
164                 // Transfer the buffer in a DirectByteBuffer if it's a HeapByteBuffer and if it's too big
165                 message = convertToDirectBuffer(writeRequest, false);
166 
167                 // We don't have anything in the writeQueue, let's try to write the
168                 // data in the channel immediately if we can
169                 int written = writeDirect(writeRequest.getMessage());
170 
171                 if (IS_DEBUG) {
172                     LOG.debug("wrote {} bytes to {}", written, this);
173                 }
174 
175                 if (written > 0) {
176                     incrementWrittenBytes(written);
177                 }
178 
179                 // Update the idle status for this session
180                 idleChecker.sessionWritten(this, System.currentTimeMillis());
181                 int remaining = message.remaining();
182 
183                 if ((written < 0) || (remaining > 0)) {
184                     // Create a DirectBuffer unconditionally
185                     convertToDirectBuffer(writeRequest, true);
186 
187                     // We have to push the request on the writeQueue
188                     writeQueue.add(writeRequest);
189 
190                     // If it wasn't, we register this session as interested to write.
191                     // It's done in atomic fashion for avoiding two concurrent registering.
192                     if (!registeredForWrite.getAndSet(true)) {
193                         flushWriteQueue();
194                     }
195                 } else {
196                     // The message has been fully written : update the stats, and signal the handler
197                     // generate the message sent event
198                     // complete the future if we have one (we should...)
199                     final DefaultWriteFuture future = (DefaultWriteFuture) writeRequest.getFuture();
200 
201                     if (future != null) {
202                         future.complete();
203                     }
204 
205                     final Object highLevel = ((DefaultWriteRequest) writeRequest).getOriginalMessage();
206 
207                     if (highLevel != null) {
208                         processMessageSent(highLevel);
209                     }
210                 }
211             } else {
212                 // Transfer the buffer in a DirectByteBuffer if it's a HeapByteBuffer
213                 message = convertToDirectBuffer(writeRequest, true);
214 
215                 // We have to push the request on the writeQueue
216                 writeQueue.add(writeRequest);
217             }
218         }
219 
220         return writeRequest;
221     }
222 
223     public abstract void flushWriteQueue();
224 
225     public void setNotRegisteredForWrite() {
226         registeredForWrite.set(false);
227     }
228 
229     protected boolean isRegisteredForWrite() {
230         return registeredForWrite.get();
231     }
232 
233     /**
234      * Get the {@link Queue} of this session. The write queue contains the pending writes.
235      * 
236      * @return the write queue of this session
237      */
238     public Queue<WriteRequest> getWriteQueue() {
239         return writeQueue;
240     }
241 
242     /**
243      * Process a write operation. This will be executed only because the session has something to write into the
244      * channel.
245      */
246     public void processWrite(SelectorLoop selectorLoop) {
247         try {
248             if (IS_DEBUG) {
249                 LOG.debug("ready for write");
250                 LOG.debug("writable session : {}", this);
251             }
252 
253             do {
254                 // get a write request from the queue. We left it in the queue,
255                 // just in case we can't write all of the message content into
256                 // the channel : we will have to retrieve the message later
257                 final WriteRequest writeRequest = writeQueue.peek();
258 
259                 if (writeRequest == null) {
260                     // Nothing to write : we are done
261                     break;
262                 }
263 
264                 // The message is necessarily a ByteBuffer at this point
265                 ByteBuffer buf = (ByteBuffer) writeRequest.getMessage();
266 
267                 // Note that if the connection is secured, the buffer
268                 // already contains encrypted data.
269 
270                 // Try to write the data, and get back the number of bytes
271                 // actually written
272                 int written = ((SocketChannel) channel).write(buf);
273 
274                 if (IS_DEBUG) {
275                     LOG.debug("wrote {} bytes to {}", written, this);
276                 }
277 
278                 if (written > 0) {
279                     incrementWrittenBytes(written);
280                 }
281 
282                 // Update the idle status for this session
283                 idleChecker.sessionWritten(this, System.currentTimeMillis());
284 
285                 // Ok, we may not have written everything. Check that.
286                 if (buf.remaining() == 0) {
287                     // completed write request, let's remove it (we use poll() instead
288                     // of remove(), because remove() may throw an exception if the
289                     // queue is empty.
290                     writeQueue.poll();
291 
292                     // complete the future if we have one (we should...)
293                     final DefaultWriteFuture future = (DefaultWriteFuture) writeRequest.getFuture();
294 
295                     if (future != null) {
296                         future.complete();
297                     }
298 
299                     // generate the message sent event
300                     final Object highLevel = ((DefaultWriteRequest) writeRequest).getOriginalMessage();
301 
302                     if (highLevel != null) {
303                         processMessageSent(highLevel);
304                     }
305                 } else {
306                     // output socket buffer is full, we need
307                     // to give up until next selection for
308                     // writing.
309                     break;
310                 }
311             } while (!writeQueue.isEmpty());
312 
313             // We may have exited from the loop for some other reason
314             // that an empty queue
315             // if the session is no more interested in writing, we need
316             // to stop listening for OP_WRITE events
317             //
318             // IMPORTANT : this section is synchronized so that the OP_WRITE flag
319             // can be set safely by both the selector thread and the writer thread.
320             synchronized (writeQueue) {
321                 if (writeQueue.isEmpty()) {
322                     if (isClosing()) {
323                         if (IS_DEBUG) {
324                             LOG.debug("closing session {} have empty write queue, so we close it", this);
325                         }
326 
327                         // we was flushing writes, now we to the close
328                         channelClose();
329                     } else {
330                         // no more write event needed
331                         selectorLoop.modifyRegistration(false, !isReadSuspended(), false, (SelectorListener) this,
332                                 channel, false);
333 
334                         // Reset the flag in IoSession too
335                         setNotRegisteredForWrite();
336                     }
337                 }
338                 // if the queue is not empty, that means we have some more data to write : 
339                 // the channel OP_WRITE interest remains as it was.
340             }
341         } catch (final IOException e) {
342             LOG.error("Exception while writing : ", e);
343             processException(e);
344         }
345     }
346 }