Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
* the TCP connection without sending a FIN or RST (common with AWS ALB, k8s ingress-nginx at
* their default 60 s idle timeout).
*/
private static final class IdleCloseHandler extends ChannelInboundHandlerAdapter {
static final class IdleCloseHandler extends ChannelInboundHandlerAdapter {

private final Channel peer;

Expand All @@ -390,6 +390,18 @@ private static final class IdleCloseHandler extends ChannelInboundHandlerAdapter
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
// Read-idle while backpressure has paused this channel is expected, not a sign of a
// dropped connection: the peer's outbound buffer crossed its high-water mark, the
// TcpTunnelHandler set autoRead=false on this side, and bytes will not be read again
// until the peer drains. Skip the close so a sustained slow consumer does not get the
// tunnel torn down underneath it.
if (!ctx.channel().config().isAutoRead()) {
LOG.log(
Level.FINE,
"TCP tunnel read-idle on {0} ignored: reads paused by backpressure",
ctx.channel());
return;
}
LOG.log(
Level.FINE,
"TCP tunnel read-idle timeout on {0}, closing both channels",
Expand Down
2 changes: 2 additions & 0 deletions java/test/org/openqa/selenium/netty/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ load("@rules_jvm_external//:defs.bzl", "artifact")
load("//java:defs.bzl", "JUNIT5_DEPS", "java_library", "java_test_suite")

SMALL_TEST_SRCS = [
"IdleCloseHandlerTest.java",
"MessageInboundConverterTest.java",
"MessageOutboundConverterTest.java",
"RequestConverterTest.java",
Expand All @@ -20,6 +21,7 @@ java_test_suite(
artifact("io.netty:netty-buffer"),
artifact("io.netty:netty-codec-http"),
artifact("io.netty:netty-common"),
artifact("io.netty:netty-handler"),
artifact("io.netty:netty-transport"),
artifact("org.junit.jupiter:junit-jupiter-api"),
artifact("org.assertj:assertj-core"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.netty.server;

import static org.assertj.core.api.Assertions.assertThat;

import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.timeout.IdleStateEvent;
import org.junit.jupiter.api.Test;

class IdleCloseHandlerTest {

@Test
void idleEventClosesBothChannels() {
EmbeddedChannel peer = new EmbeddedChannel();
EmbeddedChannel self = new EmbeddedChannel(new TcpUpgradeTunnelHandler.IdleCloseHandler(peer));

assertThat(self.config().isAutoRead()).isTrue();
self.pipeline().fireUserEventTriggered(IdleStateEvent.READER_IDLE_STATE_EVENT);

assertThat(self.isOpen()).isFalse();
assertThat(peer.isOpen()).isFalse();
}

@Test
void idleEventIsIgnoredWhileBackpressureHasPausedReads() {
EmbeddedChannel peer = new EmbeddedChannel();
EmbeddedChannel self = new EmbeddedChannel(new TcpUpgradeTunnelHandler.IdleCloseHandler(peer));

// Simulate backpressure: TcpTunnelHandler would have set autoRead=false on this channel
// because the peer's outbound buffer was full.
self.config().setAutoRead(false);

// The read-idle event must NOT tear down the tunnel — no bytes arriving is the expected
// consequence of pausing reads, not a sign of a dropped connection.
self.pipeline().fireUserEventTriggered(IdleStateEvent.READER_IDLE_STATE_EVENT);

assertThat(self.isOpen()).isTrue();
assertThat(peer.isOpen()).isTrue();
}
}
Loading