redoom opened a new pull request, #15764:
URL: https://github.com/apache/dubbo/pull/15764

   ## Problem Description
   
   When compiling Apache Dubbo with JDK 25 and using it as a dependency in 
applications running with Netty 4.1.x, the following error occurs at runtime 
during class loading:
   
   ```
   java.lang.NoClassDefFoundError: io/netty/channel/MultiThreadIoEventLoopGroup
       at 
org.apache.dubbo.remoting.transport.netty4.NettyServer.createBossGroup(NettyServer.java:154)
       ...
   Caused by: java.lang.ClassNotFoundException: 
io.netty.channel.MultiThreadIoEventLoopGroup
       at 
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
       at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
   ```
   
   **Environment:**
   
   - Compile with: JDK 25
   - Runtime Netty version: 4.1.x (in dependent applications)
   - Dubbo compile-time Netty version: 4.2.2.Final
   
   **Key observation:** The same code compiled with JDK 17 works correctly.
   
   ## Root Cause Analysis
   
   ### JVM Specification Violation
   
   According to the **JVM Specification SE 25, Section 4.7.4 - The 
StackMapTable Attribute** ([[official 
documentation](https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.7.4)](https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.7.4)):
   
   > "A StackMapTable attribute is used during the process of verification by 
type checking."
   
   And more specifically:
   
   > "A stack map frame specifies (either explicitly or implicitly) the 
bytecode offset at which it applies, and the **verification types of local 
variables and operand stack entries** for that offset."
   
   For object types, the specification defines:
   
   > "The Object_variable_info item indicates that the location has the 
verification type which is the **class represented by the CONSTANT_Class_info 
structure** found in the constant_pool table at the index given by cpool_index."
   
   ```
   Object_variable_info {
       u1 tag = ITEM_Object; /* 7 */
       u2 cpool_index;
   }
   ```
   
   ### The Problem in NettyEventLoopFactory
   
   The problematic code in `NettyEventLoopFactory.java` (lines 50-55):
   
   ```java
   public static EventLoopGroup eventLoopGroup(int threads, String 
threadFactoryName) {
       ThreadFactory threadFactory = new 
DefaultThreadFactory(threadFactoryName, true);
       return shouldEpoll()
               ? new EpollEventLoopGroup(threads, threadFactory)
               : new NioEventLoopGroup(threads, threadFactory);
   }
   ```
   
   ### JDK 25's Type Inference Behavior
   
   When JDK 25 compiles the ternary operator above, it performs **Least Upper 
Bound (LUB)** type inference:
   
   1. Branch 1 returns: `EpollEventLoopGroup`
   2. Branch 2 returns: `NioEventLoopGroup`
   3. In Netty 4.2.x, both classes extend: `MultiThreadIoEventLoopGroup`
   4. JDK 25 infers this common parent type as the LUB
   
   ### Bytecode Evidence
   
   **With ternary operator (problematic):**
   
   ```
   Bytecode analysis using javap -v:
   
     public static io.netty.channel.EventLoopGroup eventLoopGroup(int, 
java.lang.String);
       Code:
         ...
         25: goto          37     // Jump to merge point
         28: new           #29    // class 
io/netty/channel/nio/NioEventLoopGroup
         ...
         37: areturn              // Merge point
   
       StackMapTable: number_of_entries = 2
         frame_type = 252 /* append */
           offset_delta = 28
           locals = [ class java/util/concurrent/ThreadFactory ]
         frame_type = 72 /* same_locals_1_stack_item */
           stack = [ class io/netty/channel/MultiThreadIoEventLoopGroup ]  ← 
PROBLEM!
   ```
   
   The `frame_type = 72` corresponds to `same_locals_1_stack_item_frame` as 
defined in JVM Spec 4.7.4:
   
   > "The frame type same_locals_1_stack_item_frame is represented by tags in 
the range [64, 127]. This frame type indicates that the frame has exactly the 
same local variables as the previous frame and that **the operand stack has one 
entry**."
   
   ```
   same_locals_1_stack_item_frame {
       u1 frame_type = SAME_LOCALS_1_STACK_ITEM; /* 64-127 */
       verification_type_info stack[1];  ← Records the common type!
   }
   ```
   
   JDK 25 writes `MultiThreadIoEventLoopGroup` into the StackMapTable as the 
`Object_variable_info` for the operand stack entry at the merge point.
   
   ### Runtime Failure Mechanism
   
   When the JVM loads the class:
   
   1. Reads the StackMapTable attribute
   2. Encounters `Object_variable_info` pointing to 
`io/netty/channel/MultiThreadIoEventLoopGroup`
   3. Attempts to load this class for verification
   4. **Fails because Netty 4.1.x doesn't have this class** (introduced in 
Netty 4.2.0)
   5. Throws `NoClassDefFoundError` during class loading verification
   
   This is a **class loading failure**, not a runtime execution failure.
   
   ### Why JDK 17 Doesn't Have This Issue
   
   JDK 17 uses a more conservative type inference strategy:
   
   - Prefers to use the declared return type (`EventLoopGroup` interface)
   - Does not infer concrete common parent classes for StackMapTable
   - Results in less precise but more compatible bytecode
   
   ### Why if-else Solves the Problem
   
   **With if-else statement:**
   
   ```java
   if (shouldEpoll()) {
       return new EpollEventLoopGroup(threads, threadFactory);
   } else {
       return new NioEventLoopGroup(threads, threadFactory);
   }
   ```
   
   **Bytecode:**
   
   ```
     public static io.netty.channel.EventLoopGroup eventLoopGroup(int, 
java.lang.String);
       Code:
         ...
         13: ifeq          26
         16: new           #24    // class 
io/netty/channel/epoll/EpollEventLoopGroup
         ...
         25: areturn              // Direct return, no merge point
         26: new           #29    // class 
io/netty/channel/nio/NioEventLoopGroup
         ...
         35: areturn              // Direct return, no merge point
   
       StackMapTable: number_of_entries = 1
         frame_type = 252 /* append */
           offset_delta = 26
           locals = [ class java/util/concurrent/ThreadFactory ]
   ```
   
   Key difference: **No merge point**, using `same_frame` instead:
   
   > "The frame type same_frame is represented by tags in the range [0-63]. 
This frame type indicates that the frame has exactly the same local variables 
as the previous frame and that **the operand stack is empty**."
   
   ```
   same_frame {
       u1 frame_type = SAME; /* 0-63 */
   }
   ```
   
   **No operand stack entries** = No `Object_variable_info` = No class 
reference to verify = No `NoClassDefFoundError`.
   
   ## Solution
   
   Replace the ternary operator with an if-else statement in 
`NettyEventLoopFactory.eventLoopGroup()` method.
   
   **File:** 
`dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyEventLoopFactory.java`
   
   **Changes:**
   
   ```diff
     public static EventLoopGroup eventLoopGroup(int threads, String 
threadFactoryName) {
         ThreadFactory threadFactory = new 
DefaultThreadFactory(threadFactoryName, true);
   -     return shouldEpoll()
   -             ? new EpollEventLoopGroup(threads, threadFactory)
   -             : new NioEventLoopGroup(threads, threadFactory);
   +     // Use if-else instead of ternary operator to avoid JDK 25 type 
inference issue
   +     // JDK 25 compiler infers MultiThreadIoEventLoopGroup as common parent 
type in StackMapTable
   +     // which causes NoClassDefFoundError when running with Netty 4.1.x
   +     if (shouldEpoll()) {
   +         return new EpollEventLoopGroup(threads, threadFactory);
   +     } else {
   +         return new NioEventLoopGroup(threads, threadFactory);
   +     }
     }
   ```
   
   ## Impact and Risk Assessment
   
   ### Impact Scope
   
   **Affected scenarios** (ALL conditions must be met):
   
   1. ✅ Compile with JDK 25
   2. ✅ Use ternary operator returning objects
   3. ✅ The two branches have a non-interface common parent class
   4. ✅ Runtime classpath doesn't have that common parent class
   
   ### Risk Level: **Low**
   
   - **Code change**: Minimal (control flow transformation, no logic change)
   - **Test coverage**: All existing tests pass
   - **Backward compatibility**: Fully maintained (JDK 8-24 unaffected)
   - **Forward compatibility**: Enables JDK 25 support
   
   ### Benefits
   
   1. ✅ **Fixes JDK 25 compatibility**
   2. ✅ **No functional changes**
   3. ✅ **No performance impact**
   4. ✅ **Backward compatible with all JDK versions**
   5. ✅ **Prevents similar issues in the future**
   
   ## Related Issues
   
   This fix addresses the compatibility issue introduced by:
   
   - JDK 25's enhanced type inference for ternary operators
   - Netty 4.2.0's architectural change (introduced 
`MultiThreadIoEventLoopGroup`)
   
   ## References
   
   1. **JVM Specification SE 25 - StackMapTable Attribute**
      https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.7.4
   
   2. **Netty 4.2.0 Release Notes**
      https://netty.io/news/2025/04/03/4-2-0.html
   
   3. **Netty 4.2 Migration Guide**
      https://github.com/netty/netty/wiki/Netty-4.2-Migration-Guide
   
   4. **JDK 25 Release Notes**
      https://openjdk.org/projects/jdk/25/


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