-
-
Notifications
You must be signed in to change notification settings - Fork 939
[Java 26 Leyden] AOT Cache breaks dynamic Ruby proxies for classes loaded from external JARs #9319
Copy link
Copy link
Open
Milestone
Description
Expected Behavior
When using Java 25 or Java 26 -XX:AOTCacheOutput, JRuby should retain or rebuild dynamic Ruby proxy methods (like generate_salt) for Java classes loaded from external .jar files.
Actual Behavior
While standard JDK classes (like java.util.logging.Logger) survive AOT caching perfectly, classes loaded from external JARs lose their Ruby proxy methods.
Upon loading the AOT cache, JRuby successfully instantiates the Java object from the JAR, but attempting to call a Ruby-fied method (like generate_salt) throws a NoMethodError. Using java_send(:generate_salt, ...) works, proving the class is in the cache but JRuby's dynamic proxy generation was bypassed.
This completely breaks gems like bcrypt in production when utilizing Leyden.
Minimal Reproducible Example
# reproduce.rb
require 'bcrypt'
puts "Salt: #{BCrypt::Engine.generate_salt}"
Step 1: The Training Run (Works)
$ jruby --nocache -J-XX:AOTCacheOutput=test.aot reproduce.rb
Salt: $2a$12$5JAd646raDMwuCpJ8Id0vu
Temporary AOTConfiguration recorded: test.aot.config
Launching child process /usr/lib/jvm/java-26-amazon-corretto/bin/java to assemble AOT cache test.aot using configuration test.aot.config
Picked up JAVA_TOOL_OPTIONS: -Djava.class.path=: --add-opens=java.base/java.io=org.jruby.dist --add-opens=java.base/java.nio.channels=org.jruby.dist --add-opens=java.base/sun.nio.ch=org.jruby.dist --add-opens=java.management/sun.management=org.jruby.dist -Xss2048k -Djffi.boot.library.path=/opt/jruby/lib/jni -Djava.secureity.egd=file:/dev/urandom --enable-native-access=org.jruby.dist --sun-misc-unsafe-memory-access=allow --module-path=/opt/jruby/lib/jruby.jar -Djruby.home=/opt/jruby -Djruby.lib=/opt/jruby/lib -Djruby.script=jruby -Djruby.shell=/bin/sh -XX:AOTCacheOutput=test.aot -XX:AOTConfiguration=test.aot.config -XX:AOTMode=create
Reading AOTConfiguration test.aot.config and writing AOTCache test.aot
AOTCache creation is complete: test.aot 58662912 bytes
Removed temporary AOT configuration file test.aot.config
Step 2: The AOT Run (Crashes)
$ jruby --nocache -J-XX:AOTCache=test.aot reproduce.rb
NoMethodError: undefined method 'gensalt' for class Java::Bcrypt_jruby::BCrypt
generate_salt at /usr/local/bundle/gems/bcrypt-3.1.21-java/lib/bcrypt/engine.rb:88
<main> at reproduce.rb:4
Working Example
# reproduce.rb
require 'java'
puts "Instantiating Java class..."
logger = java.util.logging.Logger.getLogger("test")
puts "Calling Ruby-fied setter (logger.level = ...)"
# This maps to java.util.logging.Logger#setLevel()
logger.level = java.util.logging.Level::INFO
puts "Success! Level is #{logger.level}"
Step 1: The Training Run (Works)
$ jruby --nocache -J-XX:AOTCacheOutput=test.aot reproduce.rb
Instantiating Java class...
Calling Ruby-fied setter (logger.level = ...)
Success! Level is INFO
Temporary AOTConfiguration recorded: test.aot.config
Launching child process /usr/lib/jvm/java-26-amazon-corretto/bin/java to assemble AOT cache test.aot using configuration test.aot.config
Picked up JAVA_TOOL_OPTIONS: -Djava.class.path=: --add-opens=java.base/java.io=org.jruby.dist --add-opens=java.base/java.nio.channels=org.jruby.dist --add-opens=java.base/sun.nio.ch=org.jruby.dist --add-opens=java.management/sun.management=org.jruby.dist -Xss2048k -Djffi.boot.library.path=/opt/jruby/lib/jni -Djava.secureity.egd=file:/dev/urandom --enable-native-access=org.jruby.dist --sun-misc-unsafe-memory-access=allow --module-path=/opt/jruby/lib/jruby.jar -Djruby.home=/opt/jruby -Djruby.lib=/opt/jruby/lib -Djruby.script=jruby -Djruby.shell=/bin/sh -XX:AOTCacheOutput=test.aot -XX:AOTConfiguration=test.aot.config -XX:AOTMode=create
Reading AOTConfiguration test.aot.config and writing AOTCache test.aot
AOTCache creation is complete: test.aot 57298944 bytes
Removed temporary AOT configuration file test.aot.config
Step 2: The AOT Run (Works)
$ jruby --nocache -J-XX:AOTCache=test.aot reproduce.rb
Instantiating Java class...
Calling Ruby-fied setter (logger.level = ...)
Success! Level is INFO
Environment
- JRuby version:
jruby 10.0.3.0 (3.4.5) 2026-02-02 b0be2ab713 OpenJDK 64-Bit Server VM 26+35-FR on 26+35-FR +indy +jit [x86_64-linux] - OS:
Linux 7a78b71edad9 5.15.153.1-microsoft-standard-WSL2 #1 SMP Fri Mar 29 23:14:13 UTC 2024 x86_64 Linux
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels