uuuyuqi opened a new issue, #16201:
URL: https://github.com/apache/dubbo/issues/16201

   ### Pre-check
   
   - [x] I am sure that all the content I provide is in English.
   
   ### Search before asking
   
   - [x] I had searched in the 
[issues](https://github.com/apache/dubbo/issues?q=is%3Aissue) and found no 
similar issues.
   
   ### Apache Dubbo Component
   
   Java SDK (apache/dubbo)
   
   ### Dubbo Version
   
   Dubbo Java 3.3 (also affects 3.2.x). Netty 4.1.x.
   
   ### Steps to reproduce this issue
   
   **Scenario**: Provider has a broken Netty dependency (e.g., incompatible 
`netty-buffer` version causing `NoClassDefFoundError: Could not initialize 
class io.netty.buffer.PooledUnsafeDirectByteBuf`).
   
   1. Consumer connects to Provider — TCP three-way handshake succeeds (handled 
by OS kernel, unaffected by the Netty bug).
   2. `NettyClient.doConnect()` only waits for TCP handshake completion, so it 
considers the connection successful. `DubboInvoker` is created and added to 
`validInvokers`.
   3. TLS handshake begins asynchronously — Consumer sends `ClientHello`.
   4. Provider's Netty read loop tries to allocate a `ByteBuf` to read the 
incoming data → `NoClassDefFoundError` is thrown.
   5. Netty's `NioByteUnsafe.handleReadException()` fires 
`pipeline.fireExceptionCaught(cause)` but does **not** close the channel (Netty 
only auto-closes for `IOException` or `OutOfMemoryError`).
   6. The exception reaches `SslServerTlsHandler.exceptionCaught()`, which 
**only logs the error** — it neither closes the channel nor propagates the 
exception.
   7. The channel remains TCP-active but is completely non-functional at the 
application layer.
   8. Consumer's `DubboInvoker.isAvailable()` returns `true` (it only checks 
`channel.isActive()`), so the invoker is never removed from `validInvokers`.
   9. All RPC requests routed to this Provider time out after 10 seconds.
   
   ### What you expected to happen
   
   When `SslServerTlsHandler.exceptionCaught()` is invoked, the channel should 
be closed (via `ctx.close()`), just like the `userEventTriggered()` method in 
the **same class** already does on TLS handshake failure. This would allow:
   
   - The Consumer to detect `channelInactive` → `isConnected()=false` → 
`isAvailable()=false`
   - Dubbo's `addInvalidateInvoker` mechanism to remove the broken invoker from 
`validInvokers`
   - The self-healing loop to work as designed
   
   **Current behavior of `SslServerTlsHandler.exceptionCaught()`** (line 60-68):
   ```java
   @Override
   public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
throws Exception {
       logger.error(INTERNAL_ERROR, "unknown error in remoting module", "",
               "TLS negotiation failed when trying to accept new connection.", 
cause);
       // BUG: no ctx.close() and no ctx.fireExceptionCaught(cause)
       // The exception is silently swallowed, channel stays open but broken
   }
   ```
   
   **Compare with `userEventTriggered()` in the same class** (line 81-89), 
which correctly closes the channel:
   ```java
   } else {
       logger.error(INTERNAL_ERROR, "", "",
               "TLS negotiation failed when trying to accept new connection.",
               handshakeEvent.cause());
       ctx.close();  // ← correctly closes the channel
   }
   ```
   
   Similarly, `SslClientTlsHandler.userEventTriggered()` on the Consumer side 
fires `ctx.fireExceptionCaught()` on TLS failure but does not close the 
channel, which can also lead to half-open connections.
   
   ### Anything else
   
   **Root cause analysis:**
   
   The exception propagation chain breaks at 
`SslServerTlsHandler.exceptionCaught()`:
   
   ```
   Netty read loop: allocate ByteBuf → NoClassDefFoundError
       ↓
   NioByteUnsafe.handleReadException() → pipeline.fireExceptionCaught(cause)
       (Netty does NOT auto-close: NoClassDefFoundError is not 
IOException/OutOfMemoryError)
       ↓
   SslServerTlsHandler.exceptionCaught() → logs error, BUT:
       ✗ Does NOT call ctx.close()
       ✗ Does NOT call ctx.fireExceptionCaught(cause)
       → Exception is silently swallowed
       → Channel remains TCP-active but application-dead
       ↓
   NettyServerHandler.exceptionCaught() → NEVER reached (exception stopped 
above)
       ↓
   Consumer side: channel still active → isAvailable()=true → invoker never 
removed
       → Continuous timeout on every RPC call routed to this Provider
   ```
   
   This is not limited to `NoClassDefFoundError` — any 
non-`IOException`/non-`OutOfMemoryError` exception during the Netty read loop 
would trigger the same behavior, leaving the channel in a zombie state.
   
   ### Are you willing to submit a pull request to fix on your own?
   
   - [x] Yes I am willing to submit a pull request on my own!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://www.apache.org/foundation/policies/conduct)


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