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]

Reply via email to