He-Pin opened a new issue, #3246:
URL: https://github.com/apache/pekko/issues/3246
### Description
`TcpOutgoingConnection` leaks the `SocketChannel` file descriptor when the
connection setup fails before the `ChannelRegistration` message is received
from the selector.
### Root cause
In `TcpOutgoingConnection.scala` (lines 54-72), the `SocketChannel` is
opened in the constructor (line 44) before the actor starts processing
messages. If any operation before `channelRegistry.register()` throws (e.g., a
user-provided `SocketOption.beforeConnect`), `reportConnectFailure` catches the
exception and calls `stop(e)`.
At this point, `registration` is still `None` because the
`ChannelRegistration` message has not been received yet.
In `TcpConnection.stopWith` (lines 390-404):
```scala
registration match {
case None =>
context.stop(self) // channel NOT closed!
case Some(reg) =>
context.become(unregistering)
reg.cancelAndClose(() => self ! Unregistered) // channel IS closed
}
```
When `registration` is `None`, `stopWith` only calls `context.stop(self)`
without closing the channel.
`postStop` (lines 406-429) also fails to close the channel because the
`isCommandFailed` condition is true, routing to the `notifyInterested()` branch
instead of the `cancelAndClose` branch.
### Impact
The `SocketChannel` file descriptor is never explicitly closed. It relies on
GC finalization (via `AbstractInterruptibleChannel`'s cleaner) to eventually
close, which is non-deterministic and can lead to file descriptor exhaustion
under high connection-failure rates.
### Existing test coverage gap
`TcpIntegrationSpec.scala` (lines 187-201) tests the "reply with
CommandFailed when a connect socket option fails before connect" scenario but
does **not** verify that the underlying `SocketChannel` was closed.
### Suggested fix
In `TcpConnection.postStop`, add an explicit `channel.close()` call when
`registration` is `None` and the channel is still open:
```scala
override def postStop(): Unit = {
if (channel.isOpen) {
if (registration.isEmpty)
channel.close() // close unregistered channels directly
else
prepareAbort()
}
// ... rest of cleanup
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]