Skip to content

fix(plc4j/transports-tcp): close SocketChannel on failed bind/socket-option setup#2645

Open
mkribs wants to merge 1 commit into
apache:developfrom
mkribs:feature/tcp-transport-fd-leak-on-failed-bind
Open

fix(plc4j/transports-tcp): close SocketChannel on failed bind/socket-option setup#2645
mkribs wants to merge 1 commit into
apache:developfrom
mkribs:feature/tcp-transport-fd-leak-on-failed-bind

Conversation

@mkribs

@mkribs mkribs commented Jul 14, 2026

Copy link
Copy Markdown

TcpTransportInstance's constructor only assigned the opened SocketChannel to its field after bind(), socket options, and connect() all succeeded. If bind() or a socket-option call threw (e.g. a pinned local port still held by a previous failed attempt), the already-open channel was never closed - leaking one fd per failed attempt.

Note: connect() failures (timeout/refused) are not affected - Socket.connect(SocketAddress, int) already closes itself internally on failure (confirmed empirically). Only failures before connect() is reached (bind, socket options) leak.

Fix: hold the channel in a local variable, only promote it to the socketChannel field once every setup step succeeds; explicitly close it in the constructor's catch block otherwise.

Testing: added testConstructor_failedBind_doesNotLeakFileDescriptors, which deterministically forces a BindException (occupies a local port, configures the transport to bind to that same port) and asserts the open-file-descriptor count stays flat across repeated failures via UnixOperatingSystemMXBean. Verified the test fails without this fix (fd count grows ~1 per attempt) and passes with it, on Linux.

closes #2644

…option setup

TcpTransportInstance's constructor only assigned the opened SocketChannel
to the socketChannel field after bind(), socket options and connect() all
succeeded. If bind() or a socket-option call threw (e.g. a pinned local
port still held by a previous failed attempt), the already-open channel
was never closed - each such failure leaked one fd. connect() failures
(timeout/refused) are unaffected since Socket.connect() already closes
itself internally on failure.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a file-descriptor leak in TcpTransportInstance when socket setup fails before connect() (e.g., bind() or socket-option configuration throwing). It ensures an opened SocketChannel is closed on constructor failure, and adds a regression test to detect descriptor growth across repeated failed binds.

Changes:

  • Refactor TcpTransportInstance constructor to keep the SocketChannel in a local variable until all setup steps succeed, and explicitly close it on IOException.
  • Add a regression test that forces a deterministic BindException scenario and checks that open file descriptor counts do not grow across repeated failures.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
plc4j/transports/tcp/src/main/java/org/apache/plc4x/java/transport/tcp/TcpTransportInstance.java Ensures the SocketChannel is closed if bind/socket-option/connect setup throws before construction completes.
plc4j/transports/tcp/src/test/java/org/apache/plc4x/java/transport/tcp/TcpTransportInstanceTest.java Adds a regression test to detect fd leaks across repeated failed constructor attempts on Unix-like JVMs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +328 to +333
// Without the fix each failed constructor call leaks the SocketChannel it opened
// before bind() failed, so fd count grows by ~1 per attempt. With the fix it stays flat.
assertTrue(grown < attempts,
"Open file descriptor count grew by " + grown + " across " + attempts
+ " failed bind attempts - each failed TcpTransportInstance constructor call "
+ "is leaking its SocketChannel instead of closing it");

@sruehl sruehl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: S7 driver leaks Netty/protocol resources on repeated failed connect() attempts (orphaned resources, related to #2544)

3 participants