aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Balao <mbalao@openjdk.org>2022-09-08 10:11:45 +0000
committerVitaly Provodin <vitaly.provodin@jetbrains.com>2022-10-27 12:11:26 +0400
commit03b1bab849522ba64598ac53685057cff7870aa1 (patch)
tree5d5c0db3c29912dd554ffe91a4fad0be058c464a
parenta13ecc020a8e52d57e7493e1c4f68f400a2f457c (diff)
downloadJetBrainsRuntime-03b1bab849522ba64598ac53685057cff7870aa1.tar.gz
8286918: Better HttpServer service
Reviewed-by: andrew Backport-of: 5a9dc12e012f69b9c32788dec5b6ecdb065f791f
-rw-r--r--src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java10
-rw-r--r--src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java4
-rw-r--r--src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java96
-rw-r--r--src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java304
-rw-r--r--src/jdk.httpserver/share/classes/sun/net/httpserver/TimeSource.java30
-rw-r--r--test/jdk/com/sun/net/httpserver/bugs/6725892/Test.java6
6 files changed, 305 insertions, 145 deletions
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java
index 1107d5ef469..500edcea6b6 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/HttpConnection.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2022, 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
@@ -30,8 +30,6 @@ import javax.net.ssl.*;
import java.nio.channels.*;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
-import com.sun.net.httpserver.*;
-import com.sun.net.httpserver.spi.*;
/**
* encapsulates all the connection specific state for a HTTP/S connection
@@ -55,14 +53,14 @@ class HttpConnection {
SocketChannel chan;
SelectionKey selectionKey;
String protocol;
- long time;
- volatile long creationTime; // time this connection was created
+ long idleStartTime; // absolute time in milli seconds, starting when the connection was marked idle
+ volatile long reqStartedTime; // time when the request was initiated
volatile long rspStartedTime; // time we started writing the response
int remaining;
boolean closed = false;
Logger logger;
- public enum State {IDLE, REQUEST, RESPONSE};
+ public enum State {IDLE, REQUEST, RESPONSE, NEWLY_ACCEPTED};
volatile State state;
public String toString() {
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java
index 7433986f1e7..045d2251636 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/SSLStreams.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2022, 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
@@ -44,7 +44,6 @@ class SSLStreams {
SSLContext sslctx;
SocketChannel chan;
- TimeSource time;
ServerImpl server;
SSLEngine engine;
EngineWrapper wrapper;
@@ -56,7 +55,6 @@ class SSLStreams {
SSLStreams (ServerImpl server, SSLContext sslctx, SocketChannel chan) throws IOException {
this.server = server;
- this.time= (TimeSource)server;
this.sslctx= sslctx;
this.chan= chan;
InetSocketAddress addr =
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java
index 20fc3c61bea..303db604a19 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2022, 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
@@ -37,29 +37,35 @@ import java.security.PrivilegedAction;
@SuppressWarnings("removal")
class ServerConfig {
- private static final int DEFAULT_CLOCK_TICK = 10000 ; // 10 sec.
+ private static final int DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS = 10000 ; // 10 sec.
- /* These values must be a reasonable multiple of clockTick */
- private static final long DEFAULT_IDLE_INTERVAL = 30 ; // 5 min
+ private static final long DEFAULT_IDLE_INTERVAL_IN_SECS = 30;
+ private static final int DEFAULT_MAX_CONNECTIONS = -1 ; // no limit on maximum connections
private static final int DEFAULT_MAX_IDLE_CONNECTIONS = 200 ;
private static final long DEFAULT_MAX_REQ_TIME = -1; // default: forever
private static final long DEFAULT_MAX_RSP_TIME = -1; // default: forever
- private static final long DEFAULT_TIMER_MILLIS = 1000;
+ // default timer schedule, in milli seconds, for the timer task that's responsible for
+ // timing out request/response if max request/response time is configured
+ private static final long DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS = 1000;
private static final int DEFAULT_MAX_REQ_HEADERS = 200;
private static final long DEFAULT_DRAIN_AMOUNT = 64 * 1024;
- private static int clockTick;
- private static long idleInterval;
+ private static long idleTimerScheduleMillis;
+ private static long idleIntervalMillis;
// The maximum number of bytes to drain from an inputstream
private static long drainAmount;
+ // the maximum number of connections that the server will allow to be open
+ // after which it will no longer "accept()" any new connections, till the
+ // current connection count goes down due to completion of processing the requests
+ private static int maxConnections;
private static int maxIdleConnections;
// The maximum number of request headers allowable
private static int maxReqHeaders;
// max time a request or response is allowed to take
private static long maxReqTime;
private static long maxRspTime;
- private static long timerMillis;
+ private static long reqRspTimerScheduleMillis;
private static boolean debug;
// the value of the TCP_NODELAY socket-level option
@@ -70,11 +76,22 @@ class ServerConfig {
new PrivilegedAction<Void>() {
@Override
public Void run () {
- idleInterval = Long.getLong("sun.net.httpserver.idleInterval",
- DEFAULT_IDLE_INTERVAL) * 1000;
+ idleIntervalMillis = Long.getLong("sun.net.httpserver.idleInterval",
+ DEFAULT_IDLE_INTERVAL_IN_SECS) * 1000;
+ if (idleIntervalMillis <= 0) {
+ idleIntervalMillis = DEFAULT_IDLE_INTERVAL_IN_SECS * 1000;
+ }
+
+ idleTimerScheduleMillis = Long.getLong("sun.net.httpserver.clockTick",
+ DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS);
+ if (idleTimerScheduleMillis <= 0) {
+ // ignore zero or negative value and use the default schedule
+ idleTimerScheduleMillis = DEFAULT_IDLE_TIMER_SCHEDULE_MILLIS;
+ }
- clockTick = Integer.getInteger("sun.net.httpserver.clockTick",
- DEFAULT_CLOCK_TICK);
+ maxConnections = Integer.getInteger(
+ "jdk.httpserver.maxConnections",
+ DEFAULT_MAX_CONNECTIONS);
maxIdleConnections = Integer.getInteger(
"sun.net.httpserver.maxIdleConnections",
@@ -93,8 +110,13 @@ class ServerConfig {
maxRspTime = Long.getLong("sun.net.httpserver.maxRspTime",
DEFAULT_MAX_RSP_TIME);
- timerMillis = Long.getLong("sun.net.httpserver.timerMillis",
- DEFAULT_TIMER_MILLIS);
+ reqRspTimerScheduleMillis = Long.getLong("sun.net.httpserver.timerMillis",
+ DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS);
+ if (reqRspTimerScheduleMillis <= 0) {
+ // ignore any negative or zero value for this configuration and reset
+ // to default schedule
+ reqRspTimerScheduleMillis = DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS;
+ }
debug = Boolean.getBoolean("sun.net.httpserver.debug");
@@ -150,14 +172,34 @@ class ServerConfig {
return debug;
}
- static long getIdleInterval() {
- return idleInterval;
+ /**
+ * {@return Returns the maximum duration, in milli seconds, a connection can be idle}
+ */
+ static long getIdleIntervalMillis() {
+ return idleIntervalMillis;
+ }
+
+ /**
+ * {@return Returns the schedule, in milli seconds, for the timer task that is responsible
+ * for managing the idle connections}
+ */
+ static long getIdleTimerScheduleMillis() {
+ return idleTimerScheduleMillis;
}
- static int getClockTick() {
- return clockTick;
+ /**
+ * @return Returns the maximum number of connections that can be open at any given time.
+ * This method can return a value of 0 or negative to represent that the limit hasn't
+ * been configured.
+ */
+ static int getMaxConnections() {
+ return maxConnections;
}
+ /**
+ * @return Returns the maximum number of connections that can be idle. This method
+ * can return a value of 0 or negative.
+ */
static int getMaxIdleConnections() {
return maxIdleConnections;
}
@@ -170,16 +212,30 @@ class ServerConfig {
return maxReqHeaders;
}
+ /**
+ * @return Returns the maximum amount of time the server will wait for the request to be read
+ * completely. This method can return a value of 0 or negative to imply no maximum limit has
+ * been configured.
+ */
static long getMaxReqTime() {
return maxReqTime;
}
+ /**
+ * @return Returns the maximum amount of time the server will wait for the response to be generated
+ * for a request that is being processed. This method can return a value of 0 or negative to
+ * imply no maximum limit has been configured.
+ */
static long getMaxRspTime() {
return maxRspTime;
}
- static long getTimerMillis() {
- return timerMillis;
+ /**
+ * {@return Returns the timer schedule of the task that's responsible for timing out
+ * request/response that have been running longer than any configured timeout}
+ */
+ static long getReqRspTimerScheduleMillis() {
+ return reqRspTimerScheduleMillis;
}
static boolean noDelay() {
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
index 748dcfd4815..5555294cc27 100644
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
+++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java
@@ -25,23 +25,49 @@
package sun.net.httpserver;
-import java.net.*;
-import java.io.*;
-import java.nio.channels.*;
-import java.util.*;
-import java.util.concurrent.*;
+import com.sun.net.httpserver.Filter;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import sun.net.httpserver.HttpConnection.State;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
-import javax.net.ssl.*;
-import com.sun.net.httpserver.*;
+import java.net.BindException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
-import sun.net.httpserver.HttpConnection.State;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Executor;
/**
* Provides implementation for both HTTP and HTTPS
*/
-class ServerImpl implements TimeSource {
+class ServerImpl {
private String protocol;
private boolean https;
@@ -53,32 +79,47 @@ class ServerImpl implements TimeSource {
private ServerSocketChannel schan;
private Selector selector;
private SelectionKey listenerKey;
- private Set<HttpConnection> idleConnections;
- private Set<HttpConnection> allConnections;
+ private final Set<HttpConnection> idleConnections;
+ // connections which have been accepted() by the server but which haven't
+ // yet sent any byte on the connection yet
+ private final Set<HttpConnection> newlyAcceptedConnections;
+ private final 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 final Set<HttpConnection> reqConnections;
+ private final Set<HttpConnection> rspConnections;
private List<Event> events;
- private Object lolock = new Object();
+ private final Object lolock = new Object();
private volatile boolean finished = false;
private volatile boolean terminating = false;
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();
+ // schedule for the timer task that's responsible for idle connection management
+ static final long IDLE_TIMER_TASK_SCHEDULE = ServerConfig.getIdleTimerScheduleMillis();
+ static final int MAX_CONNECTIONS = ServerConfig.getMaxConnections();
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;
+ // schedule for the timer task that's responsible for request/response timeout management
+ static final long REQ_RSP_TIMER_SCHEDULE = ServerConfig.getReqRspTimerScheduleMillis();
+ static final long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime());
+ static final long MAX_RSP_TIME = getTimeMillis(ServerConfig.getMaxRspTime());
+ static final boolean reqRspTimeoutEnabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1;
+ // the maximum idle duration for a connection which is currently idle but has served
+ // some request in the past
+ static final long IDLE_INTERVAL = ServerConfig.getIdleIntervalMillis();
+ // the maximum idle duration for a newly accepted connection which hasn't yet received
+ // the first byte of data on that connection
+ static final long NEWLY_ACCEPTED_CONN_IDLE_INTERVAL;
+ static {
+ // the idle duration of a newly accepted connection is considered to be the least of the
+ // configured idle interval and the configured max request time (if any).
+ NEWLY_ACCEPTED_CONN_IDLE_INTERVAL = MAX_REQ_TIME > 0
+ ? Math.min(IDLE_INTERVAL, MAX_REQ_TIME)
+ : IDLE_INTERVAL;
+ }
private Timer timer, timer1;
private final Logger logger;
@@ -109,13 +150,14 @@ class ServerImpl implements TimeSource {
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.log (Level.DEBUG, "HttpServer timer1 enabled period in ms: ", TIMER_MILLIS);
+ newlyAcceptedConnections = Collections.synchronizedSet(new HashSet<>());
+ timer = new Timer ("idle-timeout-task", true);
+ timer.schedule (new IdleTimeoutTask(), IDLE_TIMER_TASK_SCHEDULE, IDLE_TIMER_TASK_SCHEDULE);
+ if (reqRspTimeoutEnabled) {
+ timer1 = new Timer ("req-rsp-timeout-task", true);
+ timer1.schedule (new ReqRspTimeoutTask(), REQ_RSP_TIMER_SCHEDULE, REQ_RSP_TIMER_SCHEDULE);
+ logger.log(Level.DEBUG, "HttpServer request/response timeout task schedule ms: ",
+ REQ_RSP_TIMER_SCHEDULE);
logger.log (Level.DEBUG, "MAX_REQ_TIME: "+MAX_REQ_TIME);
logger.log (Level.DEBUG, "MAX_RSP_TIME: "+MAX_RSP_TIME);
}
@@ -206,8 +248,9 @@ class ServerImpl implements TimeSource {
}
allConnections.clear();
idleConnections.clear();
+ newlyAcceptedConnections.clear();
timer.cancel();
- if (timer1Enabled) {
+ if (reqRspTimeoutEnabled) {
timer1.cancel();
}
if (dispatcherThread != null && dispatcherThread != Thread.currentThread()) {
@@ -270,10 +313,6 @@ class ServerImpl implements TimeSource {
});
}
- Selector getSelector () {
- return selector;
- }
-
void addEvent (Event r) {
synchronized (lolock) {
events.add (r);
@@ -283,6 +322,70 @@ class ServerImpl implements TimeSource {
/* main server listener task */
+ /**
+ * The Dispatcher is responsible for accepting any connections and then using those connections
+ * to processing any incoming requests. A connection is represented as an instance of
+ * sun.net.httpserver.HttpConnection.
+ *
+ * Connection states:
+ * An instance of HttpConnection goes through the following states:
+ *
+ * - NEWLY_ACCEPTED: A connection is marked as newly accepted as soon as the Dispatcher
+ * accept()s a connection. A newly accepted connection is added to a newlyAcceptedConnections
+ * collection. A newly accepted connection also gets added to the allConnections collection.
+ * The newlyAcceptedConnections isn't checked for any size limits, however, if the server is
+ * configured with a maximum connection limit, then the elements in the
+ * newlyAcceptedConnections will never exceed that configured limit (no explicit size checks
+ * are done on the newlyAcceptedConnections collection, since the maximum connection limit
+ * applies to connections across different connection states). A connection in NEWLY_ACCEPTED
+ * state is considered idle and is eligible for idle connection management.
+ *
+ * - REQUEST: A connection is marked to be in REQUEST state when the request processing starts
+ * on that connection. This typically happens when the first byte of data is received on a
+ * NEWLY_ACCEPTED connection or when new data arrives on a connection which was previously
+ * in IDLE state. When a connection is in REQUEST state, it implies that the connection is
+ * active and thus isn't eligible for idle connection management. If the server is configured
+ * with a maximum request timeout, then connections in REQUEST state are eligible
+ * for Request/Response timeout management.
+ *
+ * - RESPONSE: A connection is marked to be in RESPONSE state when the server has finished
+ * reading the request. A connection is RESPONSE state is considered active and isn't eligible
+ * for idle connection management. If the server is configured with a maximum response timeout,
+ * then connections in RESPONSE state are eligible for Request/Response timeout management.
+ *
+ * - IDLE: A connection is marked as IDLE when a request/response cycle (successfully) completes
+ * on that particular connection. Idle connections are held in a idleConnections collection.
+ * The idleConnections collection is limited in size and the size is decided by a server
+ * configuration. Connections in IDLE state get added to the idleConnections collection only
+ * if that collection hasn't reached the configured limit. If a connection has reached IDLE
+ * state and there's no more room in the idleConnections collection, then such a connection
+ * gets closed. Connections in idleConnections collection are eligible for idle connection
+ * management.
+ *
+ * Idle connection management:
+ * A timer task is responsible for closing idle connections. Each connection that is in a state
+ * which is eligible for idle timeout management (see above section on connection states)
+ * will have a corresponding idle expiration time associated with it. The idle timeout management
+ * task will check the expiration time of each such connection against the current time and will
+ * close the connection if the current time is either equal to or past the expiration time.
+ *
+ * Request/Response timeout management:
+ * The server can be optionally configured with a maximum request timeout and/or maximum response
+ * timeout. If either of these timeouts have been configured, then an additional timer task is
+ * run by the server. This timer task is then responsible for closing connections which have
+ * been in REQUEST or RESPONSE state for a period of time that exceeds the respective configured
+ * timeouts.
+ *
+ * Maximum connection limit management:
+ * The server can be optionally configured with a maximum connection limit. A value of 0 or
+ * negative integer is ignored and considered to represent no connection limit. In case of a
+ * positive integer value, any newly accepted connections will be first checked against the
+ * current count of established connections (held by the allConnections collection) and if the
+ * configured limit has reached, then the newly accepted connection will be closed immediately
+ * (even before setting its state to NEWLY_ACCEPTED or adding it to the newlyAcceptedConnections
+ * collection).
+ *
+ */
class Dispatcher implements Runnable {
private void handleEvent (Event r) {
@@ -336,8 +439,7 @@ class ServerImpl implements TimeSource {
SelectionKey key = chan.register (selector, SelectionKey.OP_READ);
key.attach (c);
c.selectionKey = key;
- c.time = getTime() + IDLE_INTERVAL;
- idleConnections.add (c);
+ markIdle(c);
} catch (IOException e) {
dprint(e);
logger.log (Level.TRACE, "Dispatcher(8)", e);
@@ -380,9 +482,19 @@ class ServerImpl implements TimeSource {
continue;
}
SocketChannel chan = schan.accept();
-
// optimist there's a channel
if (chan != null) {
+ if (MAX_CONNECTIONS > 0 && allConnections.size() >= MAX_CONNECTIONS) {
+ // we've hit max limit of current open connections, so we go
+ // ahead and close this connection without processing it
+ try {
+ chan.close();
+ } catch (IOException ignore) {
+ }
+ // move on to next selected key
+ continue;
+ }
+
// Set TCP_NODELAY, if appropriate
if (ServerConfig.noDelay()) {
chan.socket().setTcpNoDelay(true);
@@ -394,7 +506,7 @@ class ServerImpl implements TimeSource {
c.selectionKey = newkey;
c.setChannel (chan);
newkey.attach (c);
- requestStarted (c);
+ markNewlyAccepted(c);
allConnections.add (c);
}
} else {
@@ -405,10 +517,12 @@ class ServerImpl implements TimeSource {
key.cancel();
chan.configureBlocking (true);
- if (idleConnections.remove(conn)) {
- // was an idle connection so add it
- // to reqConnections set.
- requestStarted (conn);
+ if (newlyAcceptedConnections.remove(conn)
+ || idleConnections.remove(conn)) {
+ // was either a newly accepted connection or an idle
+ // connection. In either case, we mark that the request
+ // has now started on this connection.
+ requestStarted(conn);
}
handle (chan, conn);
} else {
@@ -490,10 +604,14 @@ class ServerImpl implements TimeSource {
case IDLE:
idleConnections.remove(conn);
break;
+ case NEWLY_ACCEPTED:
+ newlyAcceptedConnections.remove(conn);
+ break;
}
assert !reqConnections.remove(conn);
assert !rspConnections.remove(conn);
assert !idleConnections.remove(conn);
+ assert !newlyAcceptedConnections.remove(conn);
}
/* per exchange task */
@@ -653,9 +771,9 @@ class ServerImpl implements TimeSource {
rheaders.set ("Connection", "close");
} else if (chdr.equalsIgnoreCase ("keep-alive")) {
rheaders.set ("Connection", "keep-alive");
- int idle=(int)(ServerConfig.getIdleInterval()/1000);
+ int idleSeconds = (int) (ServerConfig.getIdleIntervalMillis() / 1000);
int max=ServerConfig.getMaxIdleConnections();
- String val = "timeout="+idle+", max="+max;
+ String val = "timeout="+idleSeconds+", max="+max;
rheaders.set ("Keep-Alive", val);
}
}
@@ -796,14 +914,6 @@ class ServerImpl implements TimeSource {
logger.log (Level.DEBUG, message);
}
- long getTicks() {
- return ticks;
- }
-
- public long getTime() {
- return time;
- }
-
void delay () {
Thread.yield();
try {
@@ -828,11 +938,23 @@ class ServerImpl implements TimeSource {
}
void requestStarted (HttpConnection c) {
- c.creationTime = getTime();
+ c.reqStartedTime = System.currentTimeMillis();
c.setState (State.REQUEST);
reqConnections.add (c);
}
+ void markIdle(HttpConnection c) {
+ c.idleStartTime = System.currentTimeMillis();
+ c.setState(State.IDLE);
+ idleConnections.add(c);
+ }
+
+ void markNewlyAccepted(HttpConnection c) {
+ c.idleStartTime = System.currentTimeMillis();
+ c.setState(State.NEWLY_ACCEPTED);
+ newlyAcceptedConnections.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
@@ -844,7 +966,7 @@ class ServerImpl implements TimeSource {
State s = c.getState();
assert s == State.REQUEST : "State is not REQUEST ("+s+")";
reqConnections.remove (c);
- c.rspStartedTime = getTime();
+ c.rspStartedTime = System.currentTimeMillis();
rspConnections.add (c);
c.setState (State.RESPONSE);
}
@@ -858,38 +980,58 @@ class ServerImpl implements TimeSource {
}
/**
+ * Responsible for closing connections that have been idle.
* TimerTask run every CLOCK_TICK ms
*/
- class ServerTimerTask extends TimerTask {
+ class IdleTimeoutTask extends TimerTask {
public void run () {
LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
- time = System.currentTimeMillis();
- ticks ++;
+ final long currentTime = System.currentTimeMillis();
synchronized (idleConnections) {
- for (HttpConnection c : idleConnections) {
- if (c.time <= time) {
- toClose.add (c);
+ final Iterator<HttpConnection> it = idleConnections.iterator();
+ while (it.hasNext()) {
+ final HttpConnection c = it.next();
+ if (currentTime - c.idleStartTime >= IDLE_INTERVAL) {
+ toClose.add(c);
+ it.remove();
}
}
- for (HttpConnection c : toClose) {
- idleConnections.remove (c);
- allConnections.remove (c);
- c.close();
+ }
+ // if any newly accepted connection has been idle (i.e. no byte has been sent on that
+ // connection during the configured idle timeout period) then close it as well
+ synchronized (newlyAcceptedConnections) {
+ final Iterator<HttpConnection> it = newlyAcceptedConnections.iterator();
+ while (it.hasNext()) {
+ final HttpConnection c = it.next();
+ if (currentTime - c.idleStartTime >= NEWLY_ACCEPTED_CONN_IDLE_INTERVAL) {
+ toClose.add(c);
+ it.remove();
+ }
+ }
+ }
+ for (HttpConnection c : toClose) {
+ allConnections.remove(c);
+ c.close();
+ if (logger.isLoggable(Level.TRACE)) {
+ logger.log(Level.TRACE, "Closed idle connection " + c);
}
}
}
}
- class ServerTimerTask1 extends TimerTask {
+ /**
+ * Responsible for closing connections which have timed out while in REQUEST or RESPONSE state
+ */
+ class ReqRspTimeoutTask extends TimerTask {
// runs every TIMER_MILLIS
public void run () {
LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>();
- time = System.currentTimeMillis();
+ final long currentTime = System.currentTimeMillis();
synchronized (reqConnections) {
if (MAX_REQ_TIME != -1) {
for (HttpConnection c : reqConnections) {
- if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) {
+ if (currentTime - c.reqStartedTime >= MAX_REQ_TIME) {
toClose.add (c);
}
}
@@ -905,7 +1047,7 @@ class ServerImpl implements TimeSource {
synchronized (rspConnections) {
if (MAX_RSP_TIME != -1) {
for (HttpConnection c : rspConnections) {
- if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) {
+ if (currentTime - c.rspStartedTime >= MAX_RSP_TIME) {
toClose.add (c);
}
}
@@ -920,22 +1062,18 @@ class ServerImpl implements TimeSource {
}
}
- void logStackTrace (String s) {
- logger.log (Level.TRACE, 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.log (Level.TRACE, b.toString());
- }
-
- static long getTimeMillis(long secs) {
- if (secs == -1) {
+ /**
+ * Converts and returns the passed {@code secs} as milli seconds. If the passed {@code secs}
+ * is negative or zero or if the conversion from seconds to milli seconds results in a negative
+ * number, then this method returns -1.
+ */
+ private static long getTimeMillis(long secs) {
+ if (secs <= 0) {
return -1;
- } else {
- return secs * 1000;
}
+ final long milli = secs * 1000;
+ // this handles potential numeric overflow that may have happened during conversion
+ return milli > 0 ? milli : -1;
}
/*
diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/TimeSource.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/TimeSource.java
deleted file mode 100644
index 6a6827c3845..00000000000
--- a/src/jdk.httpserver/share/classes/sun/net/httpserver/TimeSource.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2005, 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;
-
-interface TimeSource {
- public long getTime();
-}
diff --git a/test/jdk/com/sun/net/httpserver/bugs/6725892/Test.java b/test/jdk/com/sun/net/httpserver/bugs/6725892/Test.java
index f5555c98433..a4617c07a74 100644
--- a/test/jdk/com/sun/net/httpserver/bugs/6725892/Test.java
+++ b/test/jdk/com/sun/net/httpserver/bugs/6725892/Test.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2022, 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
@@ -25,8 +25,8 @@
* @test
* @bug 6725892
* @library /test/lib
- * @run main/othervm -Dsun.net.httpserver.maxReqTime=2 Test
- * @run main/othervm -Djava.net.preferIPv6Addresses=true -Dsun.net.httpserver.maxReqTime=2 Test
+ * @run main/othervm -Dsun.net.httpserver.maxReqTime=2 -Dsun.net.httpserver.clockTick=2000 Test
+ * @run main/othervm -Djava.net.preferIPv6Addresses=true -Dsun.net.httpserver.maxReqTime=2 -Dsun.net.httpserver.clockTick=2000 Test
* @summary
*/