OpenJDK / amber / amber
changeset 7271:17d3fc18872d
6725892: Http server stability issues
Reviewed-by: chegar
line wrap: on
line diff
--- a/jdk/src/share/classes/com/sun/net/httpserver/HttpsConfigurator.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/com/sun/net/httpserver/HttpsConfigurator.java Wed Nov 17 14:29:51 2010 +0000 @@ -91,6 +91,7 @@ return context; } +//BEGIN_TIGER_EXCLUDE /** * Called by the HttpsServer to configure the parameters * for a https connection currently being established. @@ -111,4 +112,5 @@ public void configure (HttpsParameters params) { params.setSSLParameters (getSSLContext().getDefaultSSLParameters()); } +//END_TIGER_EXCLUDE }
--- a/jdk/src/share/classes/com/sun/net/httpserver/HttpsParameters.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/com/sun/net/httpserver/HttpsParameters.java Wed Nov 17 14:29:51 2010 +0000 @@ -25,7 +25,9 @@ package com.sun.net.httpserver; import java.net.InetSocketAddress; +//BEGIN_TIGER_EXCLUDE import javax.net.ssl.SSLParameters; +//END_TIGER_EXCLUDE /** * Represents the set of parameters for each https @@ -67,6 +69,7 @@ */ public abstract InetSocketAddress getClientAddress(); +//BEGIN_TIGER_EXCLUDE /** * Sets the SSLParameters to use for this HttpsParameters. * The parameters must be supported by the SSLContext contained @@ -79,6 +82,7 @@ * invalid or unsupported. */ public abstract void setSSLParameters (SSLParameters params); +//END_TIGER_EXCLUDE /** * Returns a copy of the array of ciphersuites or null if none
--- a/jdk/src/share/classes/sun/net/httpserver/ChunkedInputStream.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/ChunkedInputStream.java Wed Nov 17 14:29:51 2010 +0000 @@ -110,6 +110,7 @@ if (remaining == 0) { eof = true; consumeCRLF(); + t.getServerImpl().requestCompleted (t.getConnection()); return -1; } needToReadHeader = false;
--- a/jdk/src/share/classes/sun/net/httpserver/Event.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/Event.java Wed Nov 17 14:29:51 2010 +0000 @@ -40,5 +40,7 @@ class WriteFinishedEvent extends Event { WriteFinishedEvent (ExchangeImpl t) { super (t); + assert !t.writefinished; + t.writefinished = true; } }
--- a/jdk/src/share/classes/sun/net/httpserver/ExchangeImpl.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/ExchangeImpl.java Wed Nov 17 14:29:51 2010 +0000 @@ -38,6 +38,7 @@ Headers reqHdrs, rspHdrs; Request req; String method; + boolean writefinished; URI uri; HttpConnection connection; long reqContentLen;
--- a/jdk/src/share/classes/sun/net/httpserver/FixedLengthInputStream.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/FixedLengthInputStream.java Wed Nov 17 14:29:51 2010 +0000 @@ -56,6 +56,9 @@ int n = in.read(b, off, len); if (n > -1) { remaining -= n; + if (remaining == 0) { + t.getServerImpl().requestCompleted (t.getConnection()); + } } return n; }
--- a/jdk/src/share/classes/sun/net/httpserver/HttpConnection.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/HttpConnection.java Wed Nov 17 14:29:51 2010 +0000 @@ -55,10 +55,15 @@ SelectionKey selectionKey; String protocol; long time; + volatile long creationTime; // time this connection was created + volatile long rspStartedTime; // time we started writing the response int remaining; boolean closed = false; Logger logger; + public enum State {IDLE, REQUEST, RESPONSE}; + volatile State state; + public String toString() { String s = null; if (chan != null) { @@ -78,6 +83,14 @@ context = ctx; } + State getState() { + return state; + } + + void setState (State s) { + state = s; + } + void setParameters ( InputStream in, OutputStream rawout, SocketChannel chan, SSLEngine engine, SSLStreams sslStreams, SSLContext sslContext, String protocol,
--- a/jdk/src/share/classes/sun/net/httpserver/Request.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/Request.java Wed Nov 17 14:29:51 2010 +0000 @@ -201,32 +201,22 @@ static class ReadStream extends InputStream { SocketChannel channel; - SelectorCache sc; - Selector selector; ByteBuffer chanbuf; - SelectionKey key; - int available; byte[] one; - boolean closed = false, eof = false; + private boolean closed = false, eof = false; ByteBuffer markBuf; /* reads may be satisifed from this buffer */ boolean marked; boolean reset; int readlimit; static long readTimeout; ServerImpl server; - - static { - readTimeout = ServerConfig.getReadTimeout(); - } + final static int BUFSIZE = 8 * 1024; public ReadStream (ServerImpl server, SocketChannel chan) throws IOException { this.channel = chan; this.server = server; - sc = SelectorCache.getSelectorCache(); - selector = sc.getSelector(); - chanbuf = ByteBuffer.allocate (8* 1024); - key = chan.register (selector, SelectionKey.OP_READ); - available = 0; + chanbuf = ByteBuffer.allocate (BUFSIZE); + chanbuf.clear(); one = new byte[1]; closed = marked = reset = false; } @@ -255,6 +245,12 @@ return -1; } + assert channel.isBlocking(); + + if (off < 0 || srclen < 0|| srclen > (b.length-off)) { + throw new IndexOutOfBoundsException (); + } + if (reset) { /* satisfy from markBuf */ canreturn = markBuf.remaining (); willreturn = canreturn>srclen ? srclen : canreturn; @@ -263,17 +259,19 @@ reset = false; } } else { /* satisfy from channel */ - canreturn = available(); - while (canreturn == 0 && !eof) { - block (); - canreturn = available(); + chanbuf.clear (); + if (srclen < BUFSIZE) { + chanbuf.limit (srclen); } - if (eof) { + do { + willreturn = channel.read (chanbuf); + } while (willreturn == 0); + if (willreturn == -1) { + eof = true; return -1; } - willreturn = canreturn>srclen ? srclen : canreturn; + chanbuf.flip (); chanbuf.get(b, off, willreturn); - available -= willreturn; if (marked) { /* copy into markBuf */ try { @@ -286,6 +284,11 @@ return willreturn; } + public boolean markSupported () { + return true; + } + + /* Does not query the OS socket */ public synchronized int available () throws IOException { if (closed) throw new IOException ("Stream is closed"); @@ -296,36 +299,7 @@ if (reset) return markBuf.remaining(); - if (available > 0) - return available; - - chanbuf.clear (); - available = channel.read (chanbuf); - if (available > 0) { - chanbuf.flip(); - } else if (available == -1) { - eof = true; - available = 0; - } - return available; - } - - /** - * block() only called when available==0 and buf is empty - */ - private synchronized void block () throws IOException { - long currtime = server.getTime(); - long maxtime = currtime + readTimeout; - - while (currtime < maxtime) { - if (selector.select (readTimeout) == 1) { - selector.selectedKeys().clear(); - available (); - return; - } - currtime = server.getTime(); - } - throw new SocketTimeoutException ("no data received"); + return chanbuf.remaining(); } public void close () throws IOException { @@ -333,8 +307,6 @@ return; } channel.close (); - selector.selectNow(); - sc.freeSelector(selector); closed = true; } @@ -362,23 +334,14 @@ SocketChannel channel; ByteBuffer buf; SelectionKey key; - SelectorCache sc; - Selector selector; boolean closed; byte[] one; ServerImpl server; - static long writeTimeout; - - static { - writeTimeout = ServerConfig.getWriteTimeout(); - } public WriteStream (ServerImpl server, SocketChannel channel) throws IOException { this.channel = channel; this.server = server; - sc = SelectorCache.getSelectorCache(); - selector = sc.getSelector(); - key = channel.register (selector, SelectionKey.OP_WRITE); + assert channel.isBlocking(); closed = false; one = new byte [1]; buf = ByteBuffer.allocate (4096); @@ -411,31 +374,14 @@ l -= n; if (l == 0) return; - block(); } } - void block () throws IOException { - long currtime = server.getTime(); - long maxtime = currtime + writeTimeout; - - while (currtime < maxtime) { - if (selector.select (writeTimeout) == 1) { - selector.selectedKeys().clear (); - return; - } - currtime = server.getTime(); - } - throw new SocketTimeoutException ("write blocked too long"); - } - - public void close () throws IOException { if (closed) return; + //server.logStackTrace ("Request.OS.close: isOpen="+channel.isOpen()); channel.close (); - selector.selectNow(); - sc.freeSelector(selector); closed = true; } }
--- a/jdk/src/share/classes/sun/net/httpserver/SSLStreams.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/SSLStreams.java Wed Nov 17 14:29:51 2010 +0000 @@ -53,8 +53,6 @@ EngineWrapper wrapper; OutputStream os; InputStream is; - static long readTimeout = ServerConfig.getReadTimeout(); - static long writeTimeout = ServerConfig.getWriteTimeout(); /* held by thread doing the hand-shake on this connection */ Lock handshaking = new ReentrantLock(); @@ -77,10 +75,13 @@ if (cfg != null) { Parameters params = new Parameters (cfg, addr); cfg.configure (params); +//BEGIN_TIGER_EXCLUDE SSLParameters sslParams = params.getSSLParameters(); if (sslParams != null) { engine.setSSLParameters (sslParams); - } else { + } else +//END_TIGER_EXCLUDE + { /* tiger compatibility */ if (params.getCipherSuites() != null) { try { @@ -104,7 +105,6 @@ class Parameters extends HttpsParameters { InetSocketAddress addr; - SSLParameters params; HttpsConfigurator cfg; Parameters (HttpsConfigurator cfg, InetSocketAddress addr) { @@ -117,12 +117,15 @@ public HttpsConfigurator getHttpsConfigurator() { return cfg; } +//BEGIN_TIGER_EXCLUDE + SSLParameters params; public void setSSLParameters (SSLParameters p) { params = p; } SSLParameters getSSLParameters () { return params; } +//END_TIGER_EXCLUDE } /** @@ -245,9 +248,6 @@ SocketChannel chan; SSLEngine engine; - SelectorCache sc; - Selector write_selector, read_selector; - SelectionKey wkey, rkey; Object wrapLock, unwrapLock; ByteBuffer unwrap_src, wrap_dst; boolean closed = false; @@ -260,16 +260,9 @@ unwrapLock = new Object(); unwrap_src = allocate(BufType.PACKET); wrap_dst = allocate(BufType.PACKET); - sc = SelectorCache.getSelectorCache(); - write_selector = sc.getSelector(); - wkey = chan.register (write_selector, SelectionKey.OP_WRITE); - read_selector = sc.getSelector(); - wkey = chan.register (read_selector, SelectionKey.OP_READ); } void close () throws IOException { - sc.freeSelector (write_selector); - sc.freeSelector (read_selector); } /* try to wrap and send the data in src. Handles OVERFLOW. @@ -304,15 +297,7 @@ wrap_dst.flip(); int l = wrap_dst.remaining(); assert l == r.result.bytesProduced(); - long currtime = time.getTime(); - long maxtime = currtime + writeTimeout; while (l>0) { - write_selector.select(writeTimeout); // timeout - currtime = time.getTime(); - if (currtime > maxtime) { - throw new SocketTimeoutException ("write timed out"); - } - write_selector.selectedKeys().clear(); l -= chan.write (wrap_dst); } } @@ -342,20 +327,12 @@ needData = true; } synchronized (unwrapLock) { - int x,y; + int x; do { if (needData) { - long currTime = time.getTime(); - long maxtime = currTime + readTimeout; do { - if (currTime > maxtime) { - throw new SocketTimeoutException ("read timedout"); - } - y = read_selector.select (readTimeout); - currTime = time.getTime(); - } while (y != 1); - read_selector.selectedKeys().clear(); x = chan.read (unwrap_src); + } while (x == 0); if (x == -1) { throw new IOException ("connection closed for reading"); }
--- a/jdk/src/share/classes/sun/net/httpserver/SelectorCache.java Sun Nov 14 07:22:39 2010 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package sun.net.httpserver; - -import java.util.*; -import java.nio.*; -import java.net.*; -import java.io.*; -import java.security.*; -import java.nio.channels.*; - -/* - * Implements a cache of java.nio.channels.Selector - * where Selectors are allocated on demand and placed - * in a temporary cache for a period of time, so they - * can be reused. If a period of between 2 and 4 minutes - * elapses without being used, then they are closed. - */ -public class SelectorCache { - - static SelectorCache cache = null; - - private SelectorCache () { - freeSelectors = new LinkedList<SelectorWrapper>(); - CacheCleaner c = AccessController.doPrivileged( - new PrivilegedAction<CacheCleaner>() { - public CacheCleaner run() { - CacheCleaner cleaner = new CacheCleaner(); - cleaner.setDaemon (true); - return cleaner; - } - }); - c.start(); - } - - /** - * factory method for creating single instance - */ - public static SelectorCache getSelectorCache () { - synchronized (SelectorCache.class) { - if (cache == null) { - cache = new SelectorCache (); - } - } - return cache; - } - - private static class SelectorWrapper { - private Selector sel; - private boolean deleteFlag; - private SelectorWrapper (Selector sel) { - this.sel = sel; - this.deleteFlag = false; - } - public Selector getSelector() { return sel;} - public boolean getDeleteFlag () {return deleteFlag;} - public void setDeleteFlag (boolean b) {deleteFlag = b;} - } - - /* list of free selectors. Can be re-allocated for a period - * of time, after which if not allocated will be closed - * and removed from the list (by CacheCleaner thread) - */ - LinkedList<SelectorWrapper> freeSelectors; - - synchronized Selector getSelector () throws IOException { - SelectorWrapper wrapper = null; - Selector selector; - - if (freeSelectors.size() > 0) { - wrapper = freeSelectors.remove(); - selector = wrapper.getSelector(); - } else { - selector = Selector.open(); - } - return selector; - } - - synchronized void freeSelector (Selector selector) { - freeSelectors.add (new SelectorWrapper (selector)); - } - - /* Thread ensures that entries on freeSelector list - * remain there for at least 2 minutes and no longer - * than 4 minutes. - */ - class CacheCleaner extends Thread { - public void run () { - long timeout = ServerConfig.getSelCacheTimeout() * 1000; - while (true) { - try {Thread.sleep (timeout); } catch (Exception e) {} - synchronized (freeSelectors) { - ListIterator<SelectorWrapper> l = freeSelectors.listIterator(); - while (l.hasNext()) { - SelectorWrapper w = l.next(); - if (w.getDeleteFlag()) { - /* 2nd pass. Close the selector */ - try { - w.getSelector().close(); - } catch (IOException e) {} - l.remove(); - } else { - /* 1st pass. Set the flag */ - w.setDeleteFlag (true); - } - } - } - } - } - } -}
--- a/jdk/src/share/classes/sun/net/httpserver/ServerConfig.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/ServerConfig.java Wed Nov 17 14:29:51 2010 +0000 @@ -27,6 +27,8 @@ import com.sun.net.httpserver.*; import com.sun.net.httpserver.spi.*; +import java.util.logging.Logger; +import java.security.PrivilegedAction; /** * Parameters that users will not likely need to set @@ -37,23 +39,26 @@ static int clockTick; - static int defaultClockTick = 10000 ; // 10 sec. + static final int DEFAULT_CLOCK_TICK = 10000 ; // 10 sec. /* These values must be a reasonable multiple of clockTick */ - static long defaultReadTimeout = 20 ; // 20 sec. - static long defaultWriteTimeout = 60 ; // 60 sec. - static long defaultIdleInterval = 300 ; // 5 min - static long defaultSelCacheTimeout = 120 ; // seconds - static int defaultMaxIdleConnections = 200 ; + static final long DEFAULT_IDLE_INTERVAL = 300 ; // 5 min + static final int DEFAULT_MAX_IDLE_CONNECTIONS = 200 ; - static long defaultDrainAmount = 64 * 1024; + static final long DEFAULT_MAX_REQ_TIME = -1; // default: forever + static final long DEFAULT_MAX_RSP_TIME = -1; // default: forever + static final long DEFAULT_TIMER_MILLIS = 1000; - static long readTimeout; - static long writeTimeout; + static final long DEFAULT_DRAIN_AMOUNT = 64 * 1024; + static long idleInterval; - static long selCacheTimeout; static long drainAmount; // max # of bytes to drain from an inputstream static int maxIdleConnections; + + // max time a request or response is allowed to take + static long maxReqTime; + static long maxRspTime; + static long timerMillis; static boolean debug = false; static { @@ -61,49 +66,79 @@ idleInterval = ((Long)java.security.AccessController.doPrivileged( new sun.security.action.GetLongAction( "sun.net.httpserver.idleInterval", - defaultIdleInterval))).longValue() * 1000; + DEFAULT_IDLE_INTERVAL))).longValue() * 1000; clockTick = ((Integer)java.security.AccessController.doPrivileged( new sun.security.action.GetIntegerAction( "sun.net.httpserver.clockTick", - defaultClockTick))).intValue(); + DEFAULT_CLOCK_TICK))).intValue(); maxIdleConnections = ((Integer)java.security.AccessController.doPrivileged( new sun.security.action.GetIntegerAction( "sun.net.httpserver.maxIdleConnections", - defaultMaxIdleConnections))).intValue(); - - readTimeout = ((Long)java.security.AccessController.doPrivileged( - new sun.security.action.GetLongAction( - "sun.net.httpserver.readTimeout", - defaultReadTimeout))).longValue()* 1000; - - selCacheTimeout = ((Long)java.security.AccessController.doPrivileged( - new sun.security.action.GetLongAction( - "sun.net.httpserver.selCacheTimeout", - defaultSelCacheTimeout))).longValue()* 1000; - - writeTimeout = ((Long)java.security.AccessController.doPrivileged( - new sun.security.action.GetLongAction( - "sun.net.httpserver.writeTimeout", - defaultWriteTimeout))).longValue()* 1000; + DEFAULT_MAX_IDLE_CONNECTIONS))).intValue(); drainAmount = ((Long)java.security.AccessController.doPrivileged( new sun.security.action.GetLongAction( "sun.net.httpserver.drainAmount", - defaultDrainAmount))).longValue(); + DEFAULT_DRAIN_AMOUNT))).longValue(); + + maxReqTime = ((Long)java.security.AccessController.doPrivileged( + new sun.security.action.GetLongAction( + "sun.net.httpserver.maxReqTime", + DEFAULT_MAX_REQ_TIME))).longValue(); + + maxRspTime = ((Long)java.security.AccessController.doPrivileged( + new sun.security.action.GetLongAction( + "sun.net.httpserver.maxRspTime", + DEFAULT_MAX_RSP_TIME))).longValue(); + + timerMillis = ((Long)java.security.AccessController.doPrivileged( + new sun.security.action.GetLongAction( + "sun.net.httpserver.timerMillis", + DEFAULT_TIMER_MILLIS))).longValue(); debug = ((Boolean)java.security.AccessController.doPrivileged( new sun.security.action.GetBooleanAction( "sun.net.httpserver.debug"))).booleanValue(); } - static long getReadTimeout () { - return readTimeout; - } - static long getSelCacheTimeout () { - return selCacheTimeout; + static void checkLegacyProperties (final Logger logger) { + + // legacy properties that are no longer used + // print a warning to logger if they are set. + + java.security.AccessController.doPrivileged( + new PrivilegedAction<Void>() { + public Void run () { + if (System.getProperty("sun.net.httpserver.readTimeout") + !=null) + { + logger.warning ("sun.net.httpserver.readTimeout "+ + "property is no longer used. "+ + "Use sun.net.httpserver.maxReqTime instead." + ); + } + if (System.getProperty("sun.net.httpserver.writeTimeout") + !=null) + { + logger.warning ("sun.net.httpserver.writeTimeout "+ + "property is no longer used. Use "+ + "sun.net.httpserver.maxRspTime instead." + ); + } + if (System.getProperty("sun.net.httpserver.selCacheTimeout") + !=null) + { + logger.warning ("sun.net.httpserver.selCacheTimeout "+ + "property is no longer used." + ); + } + return null; + } + } + ); } static boolean debugEnabled () { @@ -122,11 +157,19 @@ return maxIdleConnections; } - static long getWriteTimeout () { - return writeTimeout; - } - static long getDrainAmount () { return drainAmount; } + + static long getMaxReqTime () { + return maxReqTime; + } + + static long getMaxRspTime () { + return maxRspTime; + } + + static long getTimerMillis () { + return timerMillis; + } }
--- a/jdk/src/share/classes/sun/net/httpserver/ServerImpl.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/src/share/classes/sun/net/httpserver/ServerImpl.java Wed Nov 17 14:29:51 2010 +0000 @@ -37,6 +37,7 @@ import javax.net.ssl.*; import com.sun.net.httpserver.*; import com.sun.net.httpserver.spi.*; +import sun.net.httpserver.HttpConnection.State; /** * Provides implementation for both HTTP and HTTPS @@ -55,6 +56,12 @@ private SelectionKey listenerKey; private Set<HttpConnection> idleConnections; private Set<HttpConnection> allConnections; + /* following two are used to keep track of the times + * when a connection/request is first received + * and when we start to send the response + */ + private Set<HttpConnection> reqConnections; + private Set<HttpConnection> rspConnections; private List<Event> events; private Object lolock = new Object(); private volatile boolean finished = false; @@ -62,14 +69,19 @@ private boolean bound = false; private boolean started = false; private volatile long time; /* current time */ + private volatile long subticks = 0; private volatile long ticks; /* number of clock ticks since server started */ private HttpServer wrapper; final static int CLOCK_TICK = ServerConfig.getClockTick(); final static long IDLE_INTERVAL = ServerConfig.getIdleInterval(); final static int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections(); + final static long TIMER_MILLIS = ServerConfig.getTimerMillis (); + final static long MAX_REQ_TIME=getTimeMillis(ServerConfig.getMaxReqTime()); + final static long MAX_RSP_TIME=getTimeMillis(ServerConfig.getMaxRspTime()); + final static boolean timer1Enabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1; - private Timer timer; + private Timer timer, timer1; private Logger logger; ServerImpl ( @@ -79,6 +91,7 @@ this.protocol = protocol; this.wrapper = wrapper; this.logger = Logger.getLogger ("com.sun.net.httpserver"); + ServerConfig.checkLegacyProperties (logger); https = protocol.equalsIgnoreCase ("https"); this.address = addr; contexts = new ContextList(); @@ -94,9 +107,18 @@ dispatcher = new Dispatcher(); idleConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); allConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); + reqConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); + rspConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); time = System.currentTimeMillis(); timer = new Timer ("server-timer", true); timer.schedule (new ServerTimerTask(), CLOCK_TICK, CLOCK_TICK); + if (timer1Enabled) { + timer1 = new Timer ("server-timer1", true); + timer1.schedule (new ServerTimerTask1(),TIMER_MILLIS,TIMER_MILLIS); + logger.config ("HttpServer timer1 enabled period in ms: "+TIMER_MILLIS); + logger.config ("MAX_REQ_TIME: "+MAX_REQ_TIME); + logger.config ("MAX_RSP_TIME: "+MAX_RSP_TIME); + } events = new LinkedList<Event>(); logger.config ("HttpServer created "+protocol+" "+ addr); } @@ -181,6 +203,9 @@ allConnections.clear(); idleConnections.clear(); timer.cancel(); + if (timer1Enabled) { + timer1.cancel(); + } } Dispatcher dispatcher; @@ -236,13 +261,6 @@ } } - int resultSize () { - synchronized (lolock) { - return events.size (); - } - } - - /* main server listener task */ class Dispatcher implements Runnable { @@ -257,7 +275,7 @@ if (terminating && exchanges == 0) { finished = true; } - SocketChannel chan = c.getChannel(); + responseCompleted (c); LeftOverInputStream is = t.getOriginalInputStream(); if (!is.isEOF()) { t.close = true; @@ -268,17 +286,10 @@ } else { if (is.isDataBuffered()) { /* don't re-enable the interestops, just handle it */ + requestStarted (c); handle (c.getChannel(), c); } else { - /* re-enable interestops */ - SelectionKey key = c.getSelectionKey(); - if (key.isValid()) { - key.interestOps ( - key.interestOps()|SelectionKey.OP_READ - ); - } - c.time = getTime() + IDLE_INTERVAL; - idleConnections.add (c); + connsToRegister.add (c); } } } @@ -290,22 +301,51 @@ } } + final LinkedList<HttpConnection> connsToRegister = + new LinkedList<HttpConnection>(); + + void reRegister (HttpConnection c) { + /* re-register with selector */ + try { + SocketChannel chan = c.getChannel(); + chan.configureBlocking (false); + SelectionKey key = chan.register (selector, SelectionKey.OP_READ); + key.attach (c); + c.selectionKey = key; + c.time = getTime() + IDLE_INTERVAL; + idleConnections.add (c); + } catch (IOException e) { + dprint(e); + logger.log(Level.FINER, "Dispatcher(8)", e); + c.close(); + } + } + public void run() { while (!finished) { try { + ListIterator<HttpConnection> li = + connsToRegister.listIterator(); + for (HttpConnection c : connsToRegister) { + reRegister(c); + } + connsToRegister.clear(); - /* process the events list first */ + List<Event> list = null; + selector.select(1000); + synchronized (lolock) { + if (events.size() > 0) { + list = events; + events = new LinkedList<Event>(); + } + } - while (resultSize() > 0) { - Event r; - synchronized (lolock) { - r = events.remove(0); + if (list != null) { + for (Event r: list) { handleEvent (r); } } - selector.select(1000); - /* process the selected list now */ Set<SelectionKey> selected = selector.selectedKeys(); @@ -327,6 +367,7 @@ c.selectionKey = newkey; c.setChannel (chan); newkey.attach (c); + requestStarted (c); allConnections.add (c); } else { try { @@ -334,27 +375,44 @@ boolean closed; SocketChannel chan = (SocketChannel)key.channel(); HttpConnection conn = (HttpConnection)key.attachment(); - // interestOps will be restored at end of read - key.interestOps (0); + + key.cancel(); + chan.configureBlocking (true); + if (idleConnections.remove(conn)) { + // was an idle connection so add it + // to reqConnections set. + requestStarted (conn); + } handle (chan, conn); } else { assert false; } + } catch (CancelledKeyException e) { + handleException(key, null); } catch (IOException e) { - HttpConnection conn = (HttpConnection)key.attachment(); - logger.log ( - Level.FINER, "Dispatcher (2)", e - ); - conn.close(); + handleException(key, e); } } } + // call the selector just to process the cancelled keys + selector.selectNow(); + } catch (IOException e) { + logger.log (Level.FINER, "Dispatcher (4)", e); } catch (Exception e) { - logger.log (Level.FINER, "Dispatcher (3)", e); + e.printStackTrace(); + logger.log (Level.FINER, "Dispatcher (7)", e); } } } + private void handleException (SelectionKey key, Exception e) { + HttpConnection conn = (HttpConnection)key.attachment(); + if (e != null) { + logger.log (Level.FINER, "Dispatcher (2)", e); + } + closeConnection(conn); + } + public void handle (SocketChannel chan, HttpConnection conn) throws IOException { @@ -363,10 +421,10 @@ executor.execute (t); } catch (HttpError e1) { logger.log (Level.FINER, "Dispatcher (4)", e1); - conn.close(); + closeConnection(conn); } catch (IOException e) { logger.log (Level.FINER, "Dispatcher (5)", e); - conn.close(); + closeConnection(conn); } } } @@ -390,7 +448,26 @@ return logger; } - /* per exchange task */ + private void closeConnection(HttpConnection conn) { + conn.close(); + allConnections.remove(conn); + switch (conn.getState()) { + case REQUEST: + reqConnections.remove(conn); + break; + case RESPONSE: + rspConnections.remove(conn); + break; + case IDLE: + idleConnections.remove(conn); + break; + } + assert !reqConnections.remove(conn); + assert !rspConnections.remove(conn); + assert !idleConnections.remove(conn); + } + + /* per exchange task */ class Exchange implements Runnable { SocketChannel chan; @@ -450,8 +527,7 @@ requestLine = req.requestLine(); if (requestLine == null) { /* connection closed */ - connection.close(); - allConnections.remove(connection); + closeConnection(connection); return; } int space = requestLine.indexOf (' '); @@ -482,6 +558,9 @@ if (s != null) { clen = Long.parseLong(s); } + if (clen == 0) { + requestCompleted (connection); + } } ctx = contexts.findContext (protocol, uri.getPath()); if (ctx == null) { @@ -560,7 +639,7 @@ } catch (IOException e1) { logger.log (Level.FINER, "ServerImpl.Exchange (1)", e1); - connection.close(); + closeConnection(connection); } catch (NumberFormatException e3) { reject (Code.HTTP_BAD_REQUEST, requestLine, "NumberFormatException thrown"); @@ -569,7 +648,7 @@ requestLine, "URISyntaxException thrown"); } catch (Exception e4) { logger.log (Level.FINER, "ServerImpl.Exchange (2)", e4); - connection.close(); + closeConnection(connection); } } @@ -591,47 +670,60 @@ rejected = true; logReply (code, requestStr, message); sendReply ( - code, true, "<h1>"+code+Code.msg(code)+"</h1>"+message + code, false, "<h1>"+code+Code.msg(code)+"</h1>"+message ); - /* connection is already closed by sendReply, now remove it */ - allConnections.remove(connection); + closeConnection(connection); } void sendReply ( int code, boolean closeNow, String text) { try { - String s = "HTTP/1.1 " + code + Code.msg(code) + "\r\n"; + StringBuilder builder = new StringBuilder (512); + builder.append ("HTTP/1.1 ") + .append (code).append (Code.msg(code)).append ("\r\n"); + if (text != null && text.length() != 0) { - s = s + "Content-Length: "+text.length()+"\r\n"; - s = s + "Content-Type: text/html\r\n"; + builder.append ("Content-Length: ") + .append (text.length()).append ("\r\n") + .append ("Content-Type: text/html\r\n"); } else { - s = s + "Content-Length: 0\r\n"; + builder.append ("Content-Length: 0\r\n"); text = ""; } if (closeNow) { - s = s + "Connection: close\r\n"; + builder.append ("Connection: close\r\n"); } - s = s + "\r\n" + text; + builder.append ("\r\n").append (text); + String s = builder.toString(); byte[] b = s.getBytes("ISO8859_1"); rawout.write (b); rawout.flush(); if (closeNow) { - connection.close(); + closeConnection(connection); } } catch (IOException e) { logger.log (Level.FINER, "ServerImpl.sendReply", e); - connection.close(); + closeConnection(connection); } } } void logReply (int code, String requestStr, String text) { + if (!logger.isLoggable(Level.FINE)) { + return; + } if (text == null) { text = ""; } - String message = requestStr + " [" + code + " " + + String r; + if (requestStr.length() > 80) { + r = requestStr.substring (0, 80) + "<TRUNCATED>"; + } else { + r = requestStr; + } + String message = r + " [" + code + " " + Code.msg(code) + "] ("+text+")"; logger.fine (message); } @@ -667,6 +759,34 @@ return wrapper; } + void requestStarted (HttpConnection c) { + c.creationTime = getTime(); + c.setState (State.REQUEST); + reqConnections.add (c); + } + + // called after a request has been completely read + // by the server. This stops the timer which would + // close the connection if the request doesn't arrive + // quickly enough. It then starts the timer + // that ensures the client reads the response in a timely + // fashion. + + void requestCompleted (HttpConnection c) { + assert c.getState() == State.REQUEST; + reqConnections.remove (c); + c.rspStartedTime = getTime(); + rspConnections.add (c); + c.setState (State.RESPONSE); + } + + // called after response has been sent + void responseCompleted (HttpConnection c) { + assert c.getState() == State.RESPONSE; + rspConnections.remove (c); + c.setState (State.IDLE); + } + /** * TimerTask run every CLOCK_TICK ms */ @@ -689,4 +809,62 @@ } } } + + class ServerTimerTask1 extends TimerTask { + + // runs every TIMER_MILLIS + public void run () { + LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>(); + time = System.currentTimeMillis(); + synchronized (reqConnections) { + if (MAX_REQ_TIME != -1) { + for (HttpConnection c : reqConnections) { + if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) { + toClose.add (c); + } + } + for (HttpConnection c : toClose) { + logger.log (Level.FINE, "closing: no request: " + c); + reqConnections.remove (c); + allConnections.remove (c); + c.close(); + } + } + } + toClose = new LinkedList<HttpConnection>(); + synchronized (rspConnections) { + if (MAX_RSP_TIME != -1) { + for (HttpConnection c : rspConnections) { + if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) { + toClose.add (c); + } + } + for (HttpConnection c : toClose) { + logger.log (Level.FINE, "closing: no response: " + c); + rspConnections.remove (c); + allConnections.remove (c); + c.close(); + } + } + } + } + } + + void logStackTrace (String s) { + logger.finest (s); + StringBuilder b = new StringBuilder (); + StackTraceElement[] e = Thread.currentThread().getStackTrace(); + for (int i=0; i<e.length; i++) { + b.append (e[i].toString()).append("\n"); + } + logger.finest (b.toString()); + } + + static long getTimeMillis(long secs) { + if (secs == -1) { + return -1; + } else { + return secs * 1000; + } + } }
--- a/jdk/test/com/sun/net/httpserver/Test.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/test/com/sun/net/httpserver/Test.java Wed Nov 17 14:29:51 2010 +0000 @@ -22,8 +22,20 @@ */ import com.sun.net.httpserver.*; +import java.util.logging.*; public class Test { + + static Logger logger; + + static void enableLogging() { + logger = Logger.getLogger("com.sun.net.httpserver"); + Handler h = new ConsoleHandler(); + h.setLevel(Level.ALL); + logger.setLevel(Level.ALL); + logger.addHandler(h); + } + static void delay () { try { Thread.sleep (1000);
--- a/jdk/test/com/sun/net/httpserver/Test1.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/test/com/sun/net/httpserver/Test1.java Wed Nov 17 14:29:51 2010 +0000 @@ -25,6 +25,7 @@ * @test * @bug 6270015 * @run main/othervm Test1 + * @run main/othervm -Dsun.net.httpserver.maxReqTime=10 Test1 * @summary Light weight HTTP server */
--- a/jdk/test/com/sun/net/httpserver/Test13.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/test/com/sun/net/httpserver/Test13.java Wed Nov 17 14:29:51 2010 +0000 @@ -31,6 +31,7 @@ import com.sun.net.httpserver.*; import java.util.concurrent.*; +import java.util.logging.*; import java.io.*; import java.net.*; @@ -45,12 +46,19 @@ static SSLContext ctx; + final static int NUM = 32; // was 32 + static boolean fail = false; public static void main (String[] args) throws Exception { HttpServer s1 = null; HttpsServer s2 = null; ExecutorService executor=null; + Logger l = Logger.getLogger ("com.sun.net.httpserver"); + Handler ha = new ConsoleHandler(); + ha.setLevel(Level.ALL); + l.setLevel(Level.ALL); + l.addHandler(ha); try { String root = System.getProperty ("test.src")+ "/docs"; System.out.print ("Test13: "); @@ -70,10 +78,10 @@ int port = s1.getAddress().getPort(); int httpsport = s2.getAddress().getPort(); - Runner r[] = new Runner[64]; - for (int i=0; i<32; i++) { + Runner r[] = new Runner[NUM*2]; + for (int i=0; i<NUM; i++) { r[i] = new Runner (true, "http", root+"/test1", port, "smallfile.txt", 23); - r[i+32] = new Runner (true, "https", root+"/test1", port, "smallfile.txt", 23); + r[i+NUM] = new Runner (true, "https", root+"/test1", httpsport, "smallfile.txt", 23); } start (r); join (r); @@ -91,6 +99,7 @@ static void start (Runner[] x) { for (int i=0; i<x.length; i++) { + if (x[i] != null) x[i].start(); } } @@ -98,6 +107,7 @@ static void join (Runner[] x) { for (int i=0; i<x.length; i++) { try { + if (x[i] != null) x[i].join(); } catch (InterruptedException e) {} }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/com/sun/net/httpserver/bugs/6725892/Test.java Wed Nov 17 14:29:51 2010 +0000 @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 6725892 + * @run main/othervm -Dsun.net.httpserver.maxReqTime=2 Test + * @summary + */ + +import com.sun.net.httpserver.*; + +import java.util.concurrent.*; +import java.util.logging.*; +import java.io.*; +import java.net.*; +import javax.net.ssl.*; + +public class Test { + + static HttpServer s1; + static int port; + static URL url; + static final String RESPONSE_BODY = "response"; + static boolean failed = false; + + static class Handler implements HttpHandler { + + public void handle (HttpExchange t) + throws IOException + { + InputStream is = t.getRequestBody(); + InetSocketAddress rem = t.getRemoteAddress(); + System.out.println ("Request from: " + rem); + while (is.read () != -1) ; + is.close(); + String requrl = t.getRequestURI().toString(); + OutputStream os = t.getResponseBody(); + t.sendResponseHeaders (200, RESPONSE_BODY.length()); + os.write (RESPONSE_BODY.getBytes()); + t.close(); + } + } + + public static void main (String[] args) throws Exception { + + ExecutorService exec = Executors.newCachedThreadPool(); + //Logger log = Logger.getLogger ("com.sun.net.httpserver"); + //log.setLevel(Level.ALL); + //ConsoleHandler hg = new ConsoleHandler(); + //hg.setLevel (Level.ALL); + //log.addHandler(hg); + + sun.net.httpserver.HttpServerImpl x = null; + try { + InetSocketAddress addr = new InetSocketAddress (0); + s1 = HttpServer.create (addr, 0); + HttpHandler h = new Handler (); + HttpContext c1 = s1.createContext ("/", h); + s1.setExecutor(exec); + s1.start(); + + port = s1.getAddress().getPort(); + System.out.println ("Server on port " + port); + url = new URL ("http://rialto.ireland:"+port+"/foo"); + test1(); + test2(); + test3(); + x = (sun.net.httpserver.HttpServerImpl)s1; + Thread.sleep (2000); + } catch (Exception e) { + e.printStackTrace(); + System.out.println ("FAIL"); + throw new RuntimeException (); + } finally { + s1.stop(0); + System.out.println ("After Shutdown"); + exec.shutdown(); + } + } + + // open TCP connection without sending anything. Check server closes it. + + static void test1() throws IOException { + failed = false; + Socket s = new Socket ("127.0.0.1", port); + InputStream is = s.getInputStream(); + // server should close connection after 2 seconds. We wait up to 10 + s.setSoTimeout (10000); + try { + is.read(); + } catch (SocketTimeoutException e) { + failed = true; + } + s.close(); + if (failed) { + System.out.println ("test1: FAIL"); + throw new RuntimeException (); + } else { + System.out.println ("test1: OK"); + } + } + + // send request and don't read response. Check server closes connection + + static void test2() throws IOException { + HttpURLConnection urlc = (HttpURLConnection) url.openConnection(); + urlc.setReadTimeout (20 * 1000); + InputStream is = urlc.getInputStream(); + // we won't read response and check if it times out + // on server. If it timesout at client then there is a problem + try { + Thread.sleep (10 * 1000); + while (is.read() != -1) ; + } catch (InterruptedException e) { + System.out.println (e); + System.out.println ("test2: FAIL"); + throw new RuntimeException ("unexpected error"); + } catch (SocketTimeoutException e1) { + System.out.println (e1); + System.out.println ("test2: FAIL"); + throw new RuntimeException ("client timedout"); + } finally { + is.close(); + } + System.out.println ("test2: OK"); + } + + // same as test2, but repeated with multiple connections + // including a number of valid request/responses + + // Worker: a thread opens a connection to the server in one of three modes. + // NORMAL - sends a request, waits for response, and checks valid response + // REQUEST - sends a partial request, and blocks, to see if + // server closes the connection. + // RESPONSE - sends a request, partially reads response and blocks, + // to see if server closes the connection. + + static class Worker extends Thread { + CountDownLatch latch; + Mode mode; + + enum Mode { + REQUEST, // block during sending of request + RESPONSE, // block during reading of response + NORMAL // don't block + }; + + Worker (CountDownLatch latch, Mode mode) { + this.latch = latch; + this.mode = mode; + } + + void fail(String msg) { + System.out.println (msg); + failed = true; + } + + public void run () { + HttpURLConnection urlc; + InputStream is = null; + + try { + urlc = (HttpURLConnection) url.openConnection(); + urlc.setReadTimeout (20 * 1000); + urlc.setDoOutput(true); + } catch (IOException e) { + fail("Worker: failed to connect to server"); + latch.countDown(); + return; + } + try { + OutputStream os = urlc.getOutputStream(); + os.write ("foo".getBytes()); + if (mode == Mode.REQUEST) { + Thread.sleep (3000); + } + os.close(); + is = urlc.getInputStream(); + if (mode == Mode.RESPONSE) { + Thread.sleep (3000); + } + if (!checkResponse (is, RESPONSE_BODY)) { + fail ("Worker: response"); + } + is.close(); + return; + } catch (InterruptedException e0) { + fail("Worker: timedout"); + } catch (SocketTimeoutException e1) { + fail("Worker: timedout"); + } catch (IOException e2) { + switch (mode) { + case NORMAL: + fail ("Worker: " + e2.getMessage()); + break; + case RESPONSE: + if (is == null) { + fail ("Worker: " + e2.getMessage()); + break; + } + // default: is ok + } + } finally { + latch.countDown(); + } + } + } + + static final int NUM = 20; + + static void test3() throws Exception { + failed = false; + CountDownLatch l = new CountDownLatch (NUM*3); + Worker[] workers = new Worker[NUM*3]; + for (int i=0; i<NUM; i++) { + workers[i*3] = new Worker (l, Worker.Mode.NORMAL); + workers[i*3+1] = new Worker (l, Worker.Mode.REQUEST); + workers[i*3+2] = new Worker (l, Worker.Mode.RESPONSE); + workers[i*3].start(); + workers[i*3+1].start(); + workers[i*3+2].start(); + } + l.await(); + for (int i=0; i<NUM*3; i++) { + workers[i].join(); + } + if (failed) { + throw new RuntimeException ("test3: failed"); + } + System.out.println ("test3: OK"); + } + + static boolean checkResponse (InputStream is, String resp) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buf = new byte [64]; + int c; + while ((c=is.read(buf)) != -1) { + bos.write (buf, 0, c); + } + bos.close(); + if (!bos.toString().equals(resp)) { + System.out.println ("Wrong response: " + bos.toString()); + return false; + } + } catch (IOException e) { + System.out.println (e); + return false; + } + return true; + } +}
--- a/jdk/test/com/sun/net/httpserver/bugs/B6401598.java Sun Nov 14 07:22:39 2010 -0800 +++ b/jdk/test/com/sun/net/httpserver/bugs/B6401598.java Wed Nov 17 14:29:51 2010 +0000 @@ -83,7 +83,7 @@ server = HttpServer.create(new InetSocketAddress(0), 400); server.createContext("/server/", new MyHandler()); exec = Executors.newFixedThreadPool(3); - server.setExecutor(null); + server.setExecutor(exec); port = server.getAddress().getPort(); server.start();