He-Pin opened a new issue, #3692:
URL: https://github.com/apache/pekko/issues/3692

   ## Summary
   
   When `ServerSocketChannel.accept()` throws, `TcpListener` logs at ERROR and 
immediately re-arms `OP_ACCEPT` with no backoff. For a persistent accept error 
— most commonly descriptor exhaustion (`EMFILE` / `ENFILE`) — the listening 
channel stays ready, so the selector keeps firing and the listener spins in a 
tight loop emitting one ERROR per iteration.
   
   ## Evidence
   
   `actor/src/main/scala/org/apache/pekko/io/TcpListener.scala:104-107`:
   
   ```scala
     def bound(registration: ChannelRegistration): Receive = {
       case ChannelAcceptable =>
         acceptLimit = acceptAllPending(registration, acceptLimit)
         if (acceptLimit > 0) 
registration.enableInterest(SelectionKey.OP_ACCEPT)
   ```
   
   `:135-152`:
   
   ```scala
     @tailrec final def acceptAllPending(registration: ChannelRegistration, 
limit: Int): Int = {
       val socketChannel =
         if (limit > 0) {
           try channel.accept()
           catch {
             case NonFatal(e) => { log.error(e, "Accept error: could not accept 
new connection"); null }
           }
         } else null
       if (socketChannel ne null) {
         log.debug("New connection accepted")
         ...
         acceptAllPending(registration, limit - 1)
       } else if (bind.pullMode) limit
       else BatchAcceptLimit
     }
   ```
   
   On error the result is `null`, so the non-pull-mode branch returns 
`BatchAcceptLimit`, which is constrained positive — 
`actor/src/main/scala/org/apache/pekko/io/Tcp.scala:618`:
   
   ```scala
       val BatchAcceptLimit: Int = getInt("batch-accept-limit").requiring(_ > 
0, "batch-accept-limit must be > 0")
   ```
   
   `acceptLimit > 0` therefore holds and `:107` re-registers `OP_ACCEPT` at 
once. There is no `scheduleOnce`, no retry message and no backoff state 
anywhere in the file. In pull mode the incoming `limit` is returned unchanged 
(`:150`), which is likewise still positive whenever the listener was accepting.
   
   Java NIO selectors are level-triggered, and a connection that could not be 
accepted remains in the backlog, so the channel is immediately ready again.
   
   ## Impact
   
   Two compounding effects while the condition persists:
   
   - **Log flooding** — one `log.error` per accept attempt, at selector-loop 
rate. This can fill disk or drown the log that an operator needs in order to 
diagnose the underlying exhaustion.
   - **CPU spin** — the listener actor and its selector thread stay hot, 
competing with the rest of the system at exactly the moment resources are 
scarce.
   
   Note the listener does *not* stop accepting, which is the right behaviour — 
this is purely about the absence of pacing. The unrelated 
`FailedRegisterIncoming` path (`:113-118`) already handles selector capacity 
limits deliberately, by closing the connection and warning once.
   
   ## Suggested direction
   
   Distinguish transient from fatal accept failures:
   
   - On a retriable `IOException` (descriptor exhaustion, `ECONNABORTED`), do 
not re-arm immediately; schedule a re-arm after a short delay instead, and 
rate-limit or de-duplicate the log line so a sustained condition produces a 
bounded number of messages rather than one per attempt.
   - Treat `ClosedChannelException` / an already-closed selector quietly, since 
that is ordinary shutdown.
   
   A configurable delay (or reuse of an existing backoff setting) would let 
operators tune it; the default should be small enough not to cost throughput 
under a brief spike.
   
   ## Test
   
   Descriptor exhaustion is awkward to provoke portably. A test could 
substitute a `ServerSocketChannel` whose `accept()` throws a controlled 
`IOException` for the first *n* calls, and assert that the listener (a) does 
not re-arm synchronously in a loop, (b) emits a bounded number of error lines, 
and (c) resumes accepting normally once `accept()` succeeds.
   


-- 
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