Skip to content

Commit 627ef34

Browse files
myankelevEirik Bjørsnøs
authored andcommitted
8304065: HttpServer.stop should terminate immediately if no exchanges are in progress
Co-authored-by: Eirik Bjørsnøs <eirbjo@openjdk.org> Reviewed-by: dfuchs, michaelm
1 parent 39714b6 commit 627ef34

File tree

7 files changed

+482
-111
lines changed

7 files changed

+482
-111
lines changed

src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedOutputStream.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,12 +26,8 @@
2626
package sun.net.httpserver;
2727

2828
import java.io.*;
29-
import java.net.*;
3029
import java.util.Objects;
3130

32-
import com.sun.net.httpserver.*;
33-
import com.sun.net.httpserver.spi.*;
34-
3531
/**
3632
* a class which allows the caller to write an arbitrary
3733
* number of bytes to an underlying stream.
@@ -159,7 +155,7 @@ public void close () throws IOException {
159155
closed = true;
160156
}
161157

162-
WriteFinishedEvent e = new WriteFinishedEvent (t);
158+
Event e = new Event.WriteFinished(t);
163159
t.getHttpContext().getServerImpl().addEvent (e);
164160
}
165161

src/jdk.httpserver/share/classes/sun/net/httpserver/Event.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,14 +25,35 @@
2525

2626
package sun.net.httpserver;
2727

28-
import com.sun.net.httpserver.*;
29-
import com.sun.net.httpserver.spi.*;
28+
import java.util.Objects;
3029

31-
class Event {
30+
abstract sealed class Event {
3231

33-
ExchangeImpl exchange;
32+
final ExchangeImpl exchange;
3433

35-
protected Event (ExchangeImpl t) {
34+
protected Event(ExchangeImpl t) {
3635
this.exchange = t;
3736
}
37+
38+
/**
39+
* Stopping event for the http server.
40+
* The event applies to the whole server and is not tied to any particular
41+
* exchange.
42+
*/
43+
static final class StopRequested extends Event {
44+
StopRequested() {
45+
super(null);
46+
}
47+
}
48+
49+
/**
50+
* Event indicating that writing is finished for a given exchange.
51+
*/
52+
static final class WriteFinished extends Event {
53+
WriteFinished(ExchangeImpl t) {
54+
super(Objects.requireNonNull(t));
55+
assert !t.writefinished;
56+
t.writefinished = true;
57+
}
58+
}
3859
}

src/jdk.httpserver/share/classes/sun/net/httpserver/FixedLengthOutputStream.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,12 +26,8 @@
2626
package sun.net.httpserver;
2727

2828
import java.io.*;
29-
import java.net.*;
3029
import java.util.Objects;
3130

32-
import com.sun.net.httpserver.*;
33-
import com.sun.net.httpserver.spi.*;
34-
3531
/**
3632
* a class which allows the caller to write up to a defined
3733
* number of bytes to an underlying stream. The caller *must*
@@ -98,7 +94,7 @@ public void close () throws IOException {
9894
is.close();
9995
} catch (IOException e) {}
10096
}
101-
WriteFinishedEvent e = new WriteFinishedEvent (t);
97+
Event e = new Event.WriteFinished(t);
10298
t.getHttpContext().getServerImpl().addEvent (e);
10399
}
104100

src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java

Lines changed: 102 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -60,7 +60,9 @@
6060
import java.util.Set;
6161
import java.util.Timer;
6262
import java.util.TimerTask;
63+
import java.util.concurrent.CountDownLatch;
6364
import java.util.concurrent.Executor;
65+
import java.util.concurrent.TimeUnit;
6466

6567
import static java.nio.charset.StandardCharsets.ISO_8859_1;
6668
import static sun.net.httpserver.Utils.isValidName;
@@ -93,7 +95,7 @@ class ServerImpl {
9395
private final Set<HttpConnection> rspConnections;
9496
private List<Event> events;
9597
private final Object lolock = new Object();
96-
private volatile boolean finished = false;
98+
private final CountDownLatch finishedLatch = new CountDownLatch(1);
9799
private volatile boolean terminating = false;
98100
private boolean bound = false;
99101
private boolean started = false;
@@ -179,7 +181,7 @@ public void bind (InetSocketAddress addr, int backlog) throws IOException {
179181
}
180182

181183
public void start () {
182-
if (!bound || started || finished) {
184+
if (!bound || started || finished()) {
183185
throw new IllegalStateException ("server in wrong state");
184186
}
185187
if (executor == null) {
@@ -222,45 +224,75 @@ public HttpsConfigurator getHttpsConfigurator () {
222224
return httpsConfig;
223225
}
224226

227+
private final boolean finished(){
228+
// if the latch is 0, the server is finished
229+
return finishedLatch.getCount() == 0;
230+
}
231+
225232
public final boolean isFinishing() {
226-
return finished;
233+
return finished();
227234
}
228235

236+
/**
237+
* This method stops the server by adding a stop request event and
238+
* waiting for the server until the event is triggered or until the maximum delay is triggered.
239+
* <p>
240+
* This ensures that the server is stopped immediately after all exchanges are complete. HttpConnections will be forcefully closed if active exchanges do not
241+
* complete within the imparted delay.
242+
*
243+
* @param delay maximum delay to wait for exchanges completion, in seconds
244+
*/
229245
public void stop (int delay) {
230246
if (delay < 0) {
231247
throw new IllegalArgumentException ("negative delay parameter");
232248
}
249+
250+
logger.log(Level.TRACE, "stopping");
251+
// posting a stop event, which will flip finished flag if it finishes
252+
// before the timeout in this method
233253
terminating = true;
254+
255+
addEvent(new Event.StopRequested());
256+
234257
try { schan.close(); } catch (IOException e) {}
235258
selector.wakeup();
236-
long latest = System.currentTimeMillis() + delay * 1000;
237-
while (System.currentTimeMillis() < latest) {
238-
delay();
239-
if (finished) {
240-
break;
259+
260+
try {
261+
// waiting for the duration of the delay, unless released before
262+
finishedLatch.await(delay, TimeUnit.SECONDS);
263+
264+
} catch (InterruptedException e) {
265+
logger.log(Level.TRACE, "Error in awaiting the delay");
266+
267+
} finally {
268+
269+
logger.log(Level.TRACE, "closing connections");
270+
finishedLatch.countDown();
271+
selector.wakeup();
272+
synchronized (allConnections) {
273+
for (HttpConnection c : allConnections) {
274+
c.close();
275+
}
241276
}
242-
}
243-
finished = true;
244-
selector.wakeup();
245-
synchronized (allConnections) {
246-
for (HttpConnection c : allConnections) {
247-
c.close();
277+
allConnections.clear();
278+
idleConnections.clear();
279+
newlyAcceptedConnections.clear();
280+
timer.cancel();
281+
if (reqRspTimeoutEnabled) {
282+
timer1.cancel();
248283
}
249-
}
250-
allConnections.clear();
251-
idleConnections.clear();
252-
newlyAcceptedConnections.clear();
253-
timer.cancel();
254-
if (reqRspTimeoutEnabled) {
255-
timer1.cancel();
256-
}
257-
if (dispatcherThread != null && dispatcherThread != Thread.currentThread()) {
258-
try {
259-
dispatcherThread.join();
260-
} catch (InterruptedException e) {
261-
Thread.currentThread().interrupt();
262-
logger.log (Level.TRACE, "ServerImpl.stop: ", e);
284+
logger.log(Level.TRACE, "connections closed");
285+
286+
if (dispatcherThread != null && dispatcherThread != Thread.currentThread()) {
287+
logger.log(Level.TRACE, "waiting for dispatcher thread");
288+
try {
289+
dispatcherThread.join();
290+
} catch (InterruptedException e) {
291+
Thread.currentThread().interrupt();
292+
logger.log(Level.TRACE, "ServerImpl.stop: ", e);
293+
}
263294
}
295+
logger.log(Level.TRACE, "server stopped");
264296
}
265297
}
266298

@@ -382,15 +414,34 @@ void addEvent (Event r) {
382414
class Dispatcher implements Runnable {
383415

384416
private void handleEvent (Event r) {
417+
418+
// Stopping marking the state as finished if stop is requested,
419+
// termination is in progress and exchange count is 0
420+
if (r instanceof Event.StopRequested) {
421+
logger.log(Level.TRACE, "Handling Stop Requested Event");
422+
423+
// checking if terminating is set to true
424+
final boolean terminatingCopy = terminating;
425+
assert terminatingCopy;
426+
427+
if (getExchangeCount() == 0 && reqConnections.isEmpty()) {
428+
finishedLatch.countDown();
429+
} else {
430+
logger.log(Level.TRACE, "Some requests are still pending");
431+
}
432+
return;
433+
}
434+
385435
ExchangeImpl t = r.exchange;
386436
HttpConnection c = t.getConnection();
437+
387438
try {
388-
if (r instanceof WriteFinishedEvent) {
439+
if (r instanceof Event.WriteFinished) {
389440

390441
logger.log(Level.TRACE, "Write Finished");
391442
int exchanges = endExchange();
392-
if (terminating && exchanges == 0) {
393-
finished = true;
443+
if (terminating && exchanges == 0 && reqConnections.isEmpty()) {
444+
finishedLatch.countDown();
394445
}
395446
LeftOverInputStream is = t.getOriginalInputStream();
396447
if (!is.isEOF()) {
@@ -440,11 +491,12 @@ void reRegister (HttpConnection c) {
440491
}
441492

442493
public void run() {
443-
while (!finished) {
494+
// finished() will be true when there are no active exchange after terminating
495+
while (!finished()) {
444496
try {
445497
List<Event> list = null;
446498
synchronized (lolock) {
447-
if (events.size() > 0) {
499+
if (!events.isEmpty()) {
448500
list = events;
449501
events = new ArrayList<>();
450502
}
@@ -591,18 +643,18 @@ private void closeConnection(HttpConnection conn) {
591643
conn.close();
592644
allConnections.remove(conn);
593645
switch (conn.getState()) {
594-
case REQUEST:
595-
reqConnections.remove(conn);
596-
break;
597-
case RESPONSE:
598-
rspConnections.remove(conn);
599-
break;
600-
case IDLE:
601-
idleConnections.remove(conn);
602-
break;
603-
case NEWLY_ACCEPTED:
604-
newlyAcceptedConnections.remove(conn);
605-
break;
646+
case REQUEST:
647+
reqConnections.remove(conn);
648+
break;
649+
case RESPONSE:
650+
rspConnections.remove(conn);
651+
break;
652+
case IDLE:
653+
idleConnections.remove(conn);
654+
break;
655+
case NEWLY_ACCEPTED:
656+
newlyAcceptedConnections.remove(conn);
657+
break;
606658
}
607659
assert !reqConnections.remove(conn);
608660
assert !rspConnections.remove(conn);
@@ -925,19 +977,16 @@ void logReply (int code, String requestStr, String text) {
925977
logger.log (Level.DEBUG, message);
926978
}
927979

928-
void delay () {
929-
Thread.yield();
930-
try {
931-
Thread.sleep (200);
932-
} catch (InterruptedException e) {}
933-
}
934-
935980
private int exchangeCount = 0;
936981

937982
synchronized void startExchange () {
938983
exchangeCount ++;
939984
}
940985

986+
synchronized int getExchangeCount() {
987+
return exchangeCount;
988+
}
989+
941990
synchronized int endExchange () {
942991
exchangeCount --;
943992
assert exchangeCount >= 0;

src/jdk.httpserver/share/classes/sun/net/httpserver/UndefLengthOutputStream.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,12 +26,8 @@
2626
package sun.net.httpserver;
2727

2828
import java.io.*;
29-
import java.net.*;
3029
import java.util.Objects;
3130

32-
import com.sun.net.httpserver.*;
33-
import com.sun.net.httpserver.spi.*;
34-
3531
/**
3632
* a class which allows the caller to write an indefinite
3733
* number of bytes to an underlying stream , but without using
@@ -79,7 +75,7 @@ public void close () throws IOException {
7975
is.close();
8076
} catch (IOException e) {}
8177
}
82-
WriteFinishedEvent e = new WriteFinishedEvent (t);
78+
Event e = new Event.WriteFinished(t);
8379
t.getHttpContext().getServerImpl().addEvent (e);
8480
}
8581

0 commit comments

Comments
 (0)