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