1   // ========================================================================
2   // Copyright 2003-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.nio;
16  
17  import java.io.IOException;
18  import java.net.InetSocketAddress;
19  import java.net.Socket;
20  import java.nio.channels.SelectionKey;
21  import java.nio.channels.ServerSocketChannel;
22  import java.nio.channels.SocketChannel;
23  
24  import org.mortbay.io.Connection;
25  import org.mortbay.io.EndPoint;
26  import org.mortbay.io.nio.SelectChannelEndPoint;
27  import org.mortbay.io.nio.SelectorManager;
28  import org.mortbay.io.nio.SelectorManager.SelectSet;
29  import org.mortbay.jetty.HttpConnection;
30  import org.mortbay.jetty.Request;
31  import org.mortbay.thread.Timeout.Task;
32  
33  /* ------------------------------------------------------------------------------- */
34  /**
35   * Selecting NIO connector.
36   * <p>
37   * This connector uses efficient NIO buffers with a non blocking threading model. Direct NIO buffers
38   * are used and threads are only allocated to connections with requests. Synchronization is used to
39   * simulate blocking for the servlet API, and any unflushed content at the end of request handling
40   * is written asynchronously.
41   * </p>
42   * <p>
43   * This connector is best used when there are a many connections that have idle periods.
44   * </p>
45   * <p>
46   * When used with {@link org.mortbay.util.ajax.Continuation}, threadless waits are supported. When
47   * a filter or servlet calls getEvent on a Continuation, a {@link org.mortbay.jetty.RetryRequest}
48   * runtime exception is thrown to allow the thread to exit the current request handling. Jetty will
49   * catch this exception and will not send a response to the client. Instead the thread is released
50   * and the Continuation is placed on the timer queue. If the Continuation timeout expires, or it's
51   * resume method is called, then the request is again allocated a thread and the request is retried.
52   * The limitation of this approach is that request content is not available on the retried request,
53   * thus if possible it should be read after the continuation or saved as a request attribute or as the
54   * associated object of the Continuation instance.
55   * </p>
56   * 
57   * @org.apache.xbean.XBean element="nioConnector" description="Creates an NIO based socket connector"
58   * 
59   * @author gregw
60   *
61   */
62  public class SelectChannelConnector extends AbstractNIOConnector 
63  {
64      private transient ServerSocketChannel _acceptChannel;
65      private long _lowResourcesConnections;
66      private long _lowResourcesMaxIdleTime;
67  
68      private SelectorManager _manager = new SelectorManager()
69      {
70          protected SocketChannel acceptChannel(SelectionKey key) throws IOException
71          {
72              // TODO handle max connections
73              SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
74              if (channel==null)
75                  return null;
76              channel.configureBlocking(false);
77              Socket socket = channel.socket();
78              configure(socket);
79              return channel;
80          }
81  
82          public boolean dispatch(Runnable task)
83          {
84              return getThreadPool().dispatch(task);
85          }
86  
87          protected void endPointClosed(SelectChannelEndPoint endpoint)
88          {
89              // TODO handle max connections and low resources
90              connectionClosed((HttpConnection)endpoint.getConnection());
91          }
92  
93          protected void endPointOpened(SelectChannelEndPoint endpoint)
94          {
95              // TODO handle max connections and low resources
96              connectionOpened((HttpConnection)endpoint.getConnection());
97          }
98  
99          protected Connection newConnection(SocketChannel channel,SelectChannelEndPoint endpoint)
100         {
101             return SelectChannelConnector.this.newConnection(channel,endpoint);
102         }
103 
104         protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException
105         {
106             return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey);
107         }
108     };
109     
110     /* ------------------------------------------------------------------------------- */
111     /**
112      * Constructor.
113      * 
114      */
115     public SelectChannelConnector()
116     {
117     }
118     
119     /* ------------------------------------------------------------ */
120     public void accept(int acceptorID) throws IOException
121     {
122         _manager.doSelect(acceptorID);
123     }
124     
125     /* ------------------------------------------------------------ */
126     public void close() throws IOException
127     {
128         synchronized(this)
129         {
130             if (_acceptChannel != null)
131                 _acceptChannel.close();
132             _acceptChannel = null;
133         }
134     }
135     
136     /* ------------------------------------------------------------------------------- */
137     @Override
138     public void customize(EndPoint endpoint, Request request) throws IOException
139     {
140         SelectChannelEndPoint cep = ((SelectChannelEndPoint)endpoint);
141         cep.cancelIdle();
142         request.setTimeStamp(cep.getSelectSet().getNow());
143         super.customize(endpoint, request);
144     }
145     
146     /* ------------------------------------------------------------------------------- */
147     @Override
148     public void persist(EndPoint endpoint) throws IOException
149     {
150         ((SelectChannelEndPoint)endpoint).scheduleIdle();
151         super.persist(endpoint);
152     }
153 
154     /* ------------------------------------------------------------ */
155     public Object getConnection()
156     {
157         return _acceptChannel;
158     }
159 
160     /* ------------------------------------------------------------------------------- */
161     public int getLocalPort()
162     {
163         synchronized(this)
164         {
165             if (_acceptChannel==null || !_acceptChannel.isOpen())
166                 return -1;
167             return _acceptChannel.socket().getLocalPort();
168         }
169     }
170 
171     /* ------------------------------------------------------------ */
172     public void open() throws IOException
173     {
174         synchronized(this)
175         {
176             if (_acceptChannel == null)
177             {
178                 // Create a new server socket
179                 _acceptChannel = ServerSocketChannel.open();
180 
181                 // Bind the server socket to the local host and port
182                 _acceptChannel.socket().setReuseAddress(getReuseAddress());
183                 InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
184                 _acceptChannel.socket().bind(addr,getAcceptQueueSize());
185 
186                 // Set to non blocking mode
187                 _acceptChannel.configureBlocking(false);
188                 
189             }
190         }
191     }
192 
193     /* ------------------------------------------------------------ */
194     public void setMaxIdleTime(int maxIdleTime)
195     {
196         _manager.setMaxIdleTime(maxIdleTime);
197         super.setMaxIdleTime(maxIdleTime);
198     }
199 
200     /* ------------------------------------------------------------ */
201     /**
202      * @return the lowResourcesConnections
203      */
204     public long getLowResourcesConnections()
205     {
206         return _lowResourcesConnections;
207     }
208 
209     /* ------------------------------------------------------------ */
210     /**
211      * Set the number of connections, which if exceeded places this manager in low resources state.
212      * This is not an exact measure as the connection count is averaged over the select sets.
213      * @param lowResourcesConnections the number of connections
214      * @see {@link #setLowResourcesMaxIdleTime(long)}
215      */
216     public void setLowResourcesConnections(long lowResourcesConnections)
217     {
218         _lowResourcesConnections=lowResourcesConnections;
219     }
220 
221     /* ------------------------------------------------------------ */
222     /**
223      * @return the lowResourcesMaxIdleTime
224      */
225     public long getLowResourcesMaxIdleTime()
226     {
227         return _lowResourcesMaxIdleTime;
228     }
229 
230     /* ------------------------------------------------------------ */
231     /**
232      * Set the period in ms that a connection is allowed to be idle when this there are more
233      * than {@link #getLowResourcesConnections()} connections.  This allows the server to rapidly close idle connections
234      * in order to gracefully handle high load situations.
235      * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low.
236      * @see {@link #setMaxIdleTime(long)}
237      * @deprecated use {@link #setLowResourceMaxIdleTime(int)}
238      */
239     public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime)
240     {
241         _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime;
242         super.setLowResourceMaxIdleTime((int)lowResourcesMaxIdleTime); // TODO fix the name duplications
243     }
244 
245     /* ------------------------------------------------------------ */
246     /**
247      * Set the period in ms that a connection is allowed to be idle when this there are more
248      * than {@link #getLowResourcesConnections()} connections.  This allows the server to rapidly close idle connections
249      * in order to gracefully handle high load situations.
250      * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low.
251      * @see {@link #setMaxIdleTime(long)}
252      */
253     public void setLowResourceMaxIdleTime(int lowResourcesMaxIdleTime)
254     {
255         _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime;
256         super.setLowResourceMaxIdleTime(lowResourcesMaxIdleTime); 
257     }
258 
259     
260     /* ------------------------------------------------------------ */
261     /*
262      * @see org.mortbay.jetty.AbstractConnector#doStart()
263      */
264     protected void doStart() throws Exception
265     {
266         _manager.setSelectSets(getAcceptors());
267         _manager.setMaxIdleTime(getMaxIdleTime());
268         _manager.setLowResourcesConnections(getLowResourcesConnections());
269         _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime());
270         _manager.start();
271         open();
272         _manager.register(_acceptChannel);
273         super.doStart();
274     }
275 
276     /* ------------------------------------------------------------ */
277     /*
278      * @see org.mortbay.jetty.AbstractConnector#doStop()
279      */
280     protected void doStop() throws Exception
281     {
282         _manager.stop();
283         super.doStop();
284     }
285 
286     /* ------------------------------------------------------------ */
287     protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
288     {
289         return new SelectChannelEndPoint(channel,selectSet,key)
290         {
291             // TODO remove this hack
292             public boolean isReadyForDispatch()
293             {
294                 return super.isReadyForDispatch() && !((HttpConnection)getConnection()).getRequest().isSuspended();
295             }
296         };
297     }
298 
299     /* ------------------------------------------------------------------------------- */
300     protected Connection newConnection(SocketChannel channel,final SelectChannelEndPoint endpoint)
301     {
302         return new HttpConnection(SelectChannelConnector.this,endpoint,getServer())
303         {
304             /* ------------------------------------------------------------ */
305             public void cancelTimeout(Task task)
306             {
307                 endpoint.getSelectSet().cancelTimeout(task);
308             }
309 
310             /* ------------------------------------------------------------ */
311             public void scheduleTimeout(Task task, long timeoutMs)
312             {
313                 endpoint.getSelectSet().scheduleTimeout(task,timeoutMs);
314             }
315         };
316     }
317 }