View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.channel.socket.oio;
17  
18  import org.jboss.netty.channel.Channel;
19  import org.jboss.netty.channel.ChannelFuture;
20  import org.jboss.netty.channel.Channels;
21  import org.jboss.netty.channel.socket.Worker;
22  
23  import java.io.IOException;
24  import java.net.SocketTimeoutException;
25  import java.util.Queue;
26  import java.util.concurrent.ConcurrentLinkedQueue;
27  
28  import static org.jboss.netty.channel.Channels.*;
29  
30  /**
31   * Abstract base class for Oio-Worker implementations
32   *
33   * @param <C> {@link AbstractOioChannel}
34   */
35  abstract class AbstractOioWorker<C extends AbstractOioChannel> implements Worker {
36  
37      private final Queue<Runnable> eventQueue = new ConcurrentLinkedQueue<Runnable>();
38  
39      protected final C channel;
40  
41      /**
42       * If this worker has been started thread will be a reference to the thread
43       * used when starting. i.e. the current thread when the run method is executed.
44       */
45      protected volatile Thread thread;
46  
47      private volatile boolean done;
48  
49      protected AbstractOioWorker(C channel) {
50          this.channel = channel;
51          channel.worker = this;
52      }
53  
54      public void run() {
55          thread = channel.workerThread = Thread.currentThread();
56          while (channel.isOpen()) {
57              synchronized (channel.interestOpsLock) {
58                  while (!channel.isReadable()) {
59                      try {
60                          // notify() is not called at all.
61                          // close() and setInterestOps() calls Thread.interrupt()
62                          channel.interestOpsLock.wait();
63                      } catch (InterruptedException e) {
64                          if (!channel.isOpen()) {
65                              break;
66                          }
67                      }
68                  }
69              }
70  
71              boolean cont = false;
72              try {
73                  cont = process();
74              } catch (Throwable t) {
75                  boolean readTimeout = t instanceof SocketTimeoutException;
76                  if (!readTimeout && !channel.isSocketClosed()) {
77                      fireExceptionCaught(channel, t);
78                  }
79                  if (readTimeout) {
80                      // the readTimeout was triggered because of the SO_TIMEOUT,
81                      // so  just continue with the loop here
82                      cont = true;
83                  }
84              } finally {
85                  processEventQueue();
86              }
87  
88              if (!cont) {
89                  break;
90              }
91          }
92  
93          // Setting the workerThread to null will prevent any channel
94          // operations from interrupting this thread from now on.
95          channel.workerThread = null;
96  
97          // Clean up.
98          close(channel, succeededFuture(channel), true);
99  
100         // Mark the worker event loop as done so we know that we need to run tasks directly and not queue them
101         // See #287
102         done = true;
103 
104         // just to make we don't have something left
105         processEventQueue();
106     }
107 
108     static boolean isIoThread(AbstractOioChannel channel) {
109         return Thread.currentThread() == channel.workerThread;
110     }
111 
112     public void executeInIoThread(Runnable task) {
113         // check if the current thread is the worker thread
114         //
115         // Also check if the event loop of the worker is complete. If so we need to run the task now.
116         // See #287
117         if (Thread.currentThread() == thread || done) {
118             task.run();
119         } else {
120             boolean added = eventQueue.offer(task);
121 
122             if (added) {
123                 // as we set the SO_TIMEOUT to 1 second this task will get picked up in 1 second at latest
124             }
125         }
126     }
127 
128     private void processEventQueue() {
129         for (;;) {
130             final Runnable task = eventQueue.poll();
131             if (task == null) {
132                 break;
133             }
134             task.run();
135         }
136     }
137 
138     /**
139      * Process the incoming messages and also is responsible for call
140      * {@link Channels#fireMessageReceived(Channel, Object)} once a message was processed without
141      * errors.
142      *
143      * @return continue returns {@code true} as long as this worker should continue to try
144      *         processing incoming messages
145      * @throws IOException
146      */
147     abstract boolean process() throws IOException;
148 
149     static void setInterestOps(
150             AbstractOioChannel channel, ChannelFuture future, int interestOps) {
151         boolean iothread = isIoThread(channel);
152 
153         // Override OP_WRITE flag - a user cannot change this flag.
154         interestOps &= ~Channel.OP_WRITE;
155         interestOps |= channel.getInterestOps() & Channel.OP_WRITE;
156 
157         boolean changed = false;
158         try {
159             if (channel.getInterestOps() != interestOps) {
160                 if ((interestOps & Channel.OP_READ) != 0) {
161                     channel.setInterestOpsNow(Channel.OP_READ);
162                 } else {
163                     channel.setInterestOpsNow(Channel.OP_NONE);
164                 }
165                 changed = true;
166             }
167 
168             future.setSuccess();
169             if (changed) {
170                 synchronized (channel.interestOpsLock) {
171                     channel.setInterestOpsNow(interestOps);
172 
173                     // Notify the worker so it stops or continues reading.
174                     Thread currentThread = Thread.currentThread();
175                     Thread workerThread = channel.workerThread;
176                     if (workerThread != null && currentThread != workerThread) {
177                         workerThread.interrupt();
178                     }
179                 }
180                 if (iothread) {
181                     fireChannelInterestChanged(channel);
182                 } else {
183                     fireChannelInterestChangedLater(channel);
184                 }
185             }
186         } catch (Throwable t) {
187             future.setFailure(t);
188             if (iothread) {
189                 fireExceptionCaught(channel, t);
190             } else {
191                 fireExceptionCaughtLater(channel, t);
192             }
193         }
194     }
195 
196     static void close(AbstractOioChannel channel, ChannelFuture future) {
197         close(channel, future, isIoThread(channel));
198     }
199 
200     private static void close(AbstractOioChannel channel, ChannelFuture future, boolean iothread) {
201         boolean connected = channel.isConnected();
202         boolean bound = channel.isBound();
203 
204         try {
205             channel.closeSocket();
206             if (channel.setClosed()) {
207                 future.setSuccess();
208                 if (connected) {
209                     // Notify the worker so it stops reading.
210                     Thread currentThread = Thread.currentThread();
211                     Thread workerThread = channel.workerThread;
212                     if (workerThread != null && currentThread != workerThread) {
213                         workerThread.interrupt();
214                     }
215                     if (iothread) {
216                         fireChannelDisconnected(channel);
217                     } else {
218                         fireChannelDisconnectedLater(channel);
219                     }
220                 }
221                 if (bound) {
222                     if (iothread) {
223                         fireChannelUnbound(channel);
224                     } else {
225                         fireChannelUnboundLater(channel);
226                     }
227                 }
228                 if (iothread) {
229                     fireChannelClosed(channel);
230                 } else {
231                     fireChannelClosedLater(channel);
232                 }
233             } else {
234                 future.setSuccess();
235             }
236         } catch (Throwable t) {
237             future.setFailure(t);
238             if (iothread) {
239                 fireExceptionCaught(channel, t);
240             } else {
241                 fireExceptionCaughtLater(channel, t);
242             }
243         }
244     }
245 }