Skip to content

Commit 7b11325

Browse files
committed
add per-stream idle timeout and suppress CancelledKeyException on cancel
; Handle cancelled SelectionKey in interestOps access
1 parent 26993ee commit 7b11325

6 files changed

Lines changed: 215 additions & 429 deletions

File tree

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/H2StreamTimeoutException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
*/
2727
package org.apache.hc.core5.http2;
2828

29-
import java.net.SocketTimeoutException;
29+
import java.io.InterruptedIOException;
3030

3131
import org.apache.hc.core5.util.Timeout;
3232

@@ -51,7 +51,7 @@
5151
*
5252
* @since 5.4
5353
*/
54-
public class H2StreamTimeoutException extends SocketTimeoutException {
54+
public class H2StreamTimeoutException extends InterruptedIOException {
5555

5656
private static final long serialVersionUID = 1L;
5757

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -439,10 +439,6 @@ public final void onInput(final ByteBuffer src) throws HttpException, IOExceptio
439439
for (;;) {
440440
final RawFrame frame = inputBuffer.read(src, ioSession);
441441
if (frame != null) {
442-
if (connState.compareTo(ConnectionHandshake.SHUTDOWN) < 0) {
443-
checkStreamTimeouts(System.nanoTime());
444-
}
445-
446442
if (streamListener != null) {
447443
streamListener.onFrameInput(this, frame.getStreamId(), frame);
448444
}
@@ -655,7 +651,6 @@ private void executeRequest(final RequestExecutionCommand requestExecutionComman
655651
requestExecutionCommand.getExchangeHandler(),
656652
requestExecutionCommand.getPushHandlerFactory(),
657653
requestExecutionCommand.getContext()));
658-
initializeStreamTimeouts(stream);
659654

660655
if (streamListener != null) {
661656
final int initInputWindow = stream.getInputWindow().get();
@@ -774,12 +769,10 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
774769
final H2StreamChannel channel = createChannel(streamId);
775770
if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) {
776771
stream = streams.createActive(channel, incomingRequest(channel));
777-
initializeStreamTimeouts(stream);
778772
streams.resetIfExceedsMaxConcurrentLimit(stream, localConfig.getMaxConcurrentStreams());
779773
} else {
780774
channel.localReset(H2Error.REFUSED_STREAM);
781775
stream = streams.createActive(channel, NoopH2StreamHandler.INSTANCE);
782-
initializeStreamTimeouts(stream);
783776
}
784777
} else if (stream.isLocalClosed() && stream.isRemoteClosed()) {
785778
throw new H2ConnectionException(H2Error.STREAM_CLOSED, "Stream closed");
@@ -970,7 +963,6 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
970963
channel.localReset(H2Error.REFUSED_STREAM);
971964
promisedStream = streams.createActive(channel, NoopH2StreamHandler.INSTANCE);
972965
}
973-
initializeStreamTimeouts(promisedStream);
974966
try {
975967
consumePushPromiseFrame(frame, payload, promisedStream);
976968
} catch (final H2StreamResetException ex) {
@@ -1376,16 +1368,8 @@ H2StreamChannel createChannel(final int streamId) {
13761368
return new H2StreamChannelImpl(streamId, initInputWinSize, initOutputWinSize);
13771369
}
13781370

1379-
private void initializeStreamTimeouts(final H2Stream stream) {
1380-
final Timeout socketTimeout = ioSession.getSocketTimeout();
1381-
if (socketTimeout != null && socketTimeout.isEnabled()) {
1382-
stream.setIdleTimeout(socketTimeout);
1383-
}
1384-
}
1385-
13861371
H2Stream createStream(final H2StreamChannel channel, final H2StreamHandler streamHandler) {
13871372
final H2Stream stream = streams.createActive(channel, streamHandler);
1388-
initializeStreamTimeouts(stream);
13891373
return stream;
13901374
}
13911375

@@ -1489,7 +1473,6 @@ public void push(final List<Header> headers, final AsyncPushProducer pushProduce
14891473
final int promisedStreamId = streams.generateStreamId();
14901474
final H2StreamChannel channel = createChannel(promisedStreamId);
14911475
final H2Stream stream = streams.createReserved(channel, outgoingPushPromise(channel, pushProducer));
1492-
initializeStreamTimeouts(stream);
14931476

14941477
commitPushPromise(id, promisedStreamId, headers);
14951478
stream.markRemoteClosed();
@@ -1614,43 +1597,21 @@ private void checkStreamTimeouts(final long nowNanos) throws IOException {
16141597
}
16151598

16161599
final Timeout idleTimeout = stream.getIdleTimeout();
1617-
final Timeout lifetimeTimeout = stream.getLifetimeTimeout();
1618-
if ((idleTimeout == null || !idleTimeout.isEnabled())
1619-
&& (lifetimeTimeout == null || !lifetimeTimeout.isEnabled())) {
1600+
if (idleTimeout == null || !idleTimeout.isEnabled()) {
16201601
continue;
16211602
}
16221603

1623-
final long created = stream.getCreatedNanos();
16241604
final long last = stream.getLastActivityNanos();
1625-
1626-
if (idleTimeout != null && idleTimeout.isEnabled()) {
1627-
final long idleNanos = idleTimeout.toNanoseconds();
1628-
if (idleNanos > 0 && nowNanos - last > idleNanos) {
1629-
final int streamId = stream.getId();
1630-
final H2StreamTimeoutException ex = new H2StreamTimeoutException(
1631-
"HTTP/2 stream idle timeout (" + idleTimeout + ")",
1632-
streamId,
1633-
idleTimeout,
1634-
true);
1635-
stream.localReset(ex, H2Error.CANCEL);
1636-
// Once reset due to idle timeout, we do not care about lifetime anymore
1637-
continue;
1638-
}
1639-
}
1640-
1641-
if (lifetimeTimeout != null && lifetimeTimeout.isEnabled()) {
1642-
final long lifeNanos = lifetimeTimeout.toNanoseconds();
1643-
if (lifeNanos > 0 && nowNanos - created > lifeNanos) {
1644-
final int streamId = stream.getId();
1645-
final H2StreamTimeoutException ex = new H2StreamTimeoutException(
1646-
"HTTP/2 stream lifetime timeout (" + lifetimeTimeout + ")",
1647-
streamId,
1648-
lifetimeTimeout,
1649-
false);
1650-
stream.localReset(ex, H2Error.CANCEL);
1651-
}
1605+
final long idleNanos = idleTimeout.toNanoseconds();
1606+
if (idleNanos > 0 && nowNanos - last > idleNanos) {
1607+
final int streamId = stream.getId();
1608+
final H2StreamTimeoutException ex = new H2StreamTimeoutException(
1609+
"HTTP/2 stream idle timeout (" + idleTimeout + ")",
1610+
streamId,
1611+
idleTimeout,
1612+
true);
1613+
stream.localReset(ex, H2Error.CANCEL);
16521614
}
16531615
}
16541616
}
1655-
16561617
}

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Stream.java

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,9 @@ class H2Stream implements StreamControl {
6161
private volatile boolean reserved;
6262
private volatile boolean remoteClosed;
6363

64-
private volatile long createdNanos;
6564
private volatile long lastActivityNanos;
6665

6766
private volatile Timeout idleTimeout;
68-
private volatile Timeout lifetimeTimeout;
6967

7068
H2Stream(final H2StreamChannel channel, final H2StreamHandler handler, final Consumer<State> stateChangeCallback) {
7169
this.channel = channel;
@@ -75,7 +73,6 @@ class H2Stream implements StreamControl {
7573
this.transitionRef = new AtomicReference<>(State.RESERVED);
7674
this.released = new AtomicBoolean();
7775
this.cancelled = new AtomicBoolean();
78-
this.createdNanos = 0L;
7976
this.lastActivityNanos = 0L;
8077
}
8178

@@ -91,7 +88,7 @@ public State getState() {
9188

9289
@Override
9390
public void setTimeout(final Timeout timeout) {
94-
// not supported
91+
this.idleTimeout = timeout;
9592
}
9693

9794
boolean isReserved() {
@@ -219,16 +216,13 @@ boolean isOutputReady() {
219216

220217
void produceOutput() throws HttpException, IOException {
221218
try {
222-
touch();
223-
224219
handler.produceOutput();
225220
} catch (final ProtocolException ex) {
226221
localReset(ex, H2Error.PROTOCOL_ERROR);
227222
}
228223
}
229224

230225
void produceInputCapacityUpdate() throws IOException {
231-
touch();
232226
handler.updateInputCapacity();
233227
}
234228

@@ -327,19 +321,13 @@ public String toString() {
327321
}
328322

329323
private void markCreatedAndActive() {
330-
final long now = System.nanoTime();
331-
this.createdNanos = now;
332-
this.lastActivityNanos = now;
324+
this.lastActivityNanos = System.nanoTime();
333325
}
334326

335327
private void touch() {
336328
this.lastActivityNanos = System.nanoTime();
337329
}
338330

339-
long getCreatedNanos() {
340-
return createdNanos;
341-
}
342-
343331
long getLastActivityNanos() {
344332
return lastActivityNanos;
345333
}
@@ -348,15 +336,4 @@ Timeout getIdleTimeout() {
348336
return idleTimeout;
349337
}
350338

351-
void setIdleTimeout(final Timeout idleTimeout) {
352-
this.idleTimeout = idleTimeout;
353-
}
354-
355-
Timeout getLifetimeTimeout() {
356-
return lifetimeTimeout;
357-
}
358-
359-
void setLifetimeTimeout(final Timeout lifetimeTimeout) {
360-
this.lifetimeTimeout = lifetimeTimeout;
361-
}
362339
}

0 commit comments

Comments
 (0)