Environment Information
- JRuby version:
9.4.14.0
- Operating system:
Linux x86_64, macOS 26.2 aarch64
- Context: Embedding JRuby using JSR223
Description
We observed that when using ScriptingContainer with LocalContextScope.CONCURRENT, global variables are unexpectedly cleared or reset to nil under concurrent load. The issue disappears completely when switching to LocalContextScope.SINGLETON.
According to the documentation (https://github.com/jruby/jruby/wiki/RedBridge#user-content-Context_Instance_Type), global variables are not supposed to be thread-local in the embed API, yet they seem to be removed when a thread-local context terminates.
Expected Behavior
Global variables defined in the container should persist and remain accessible to other threads, even if the specific thread that accessed or modified them has terminated. The lifecycle of global variables should be bound to the container/runtime, not the individual thread context in CONCURRENT mode.
Actual Behavior
When threads calling into the container terminate, the cleanup process appears to be too aggressive and removes global variables from the associated JRuby runtime. Consequently, if the container is still in use, other active threads see these variables suddenly become nil.
Reproduction
I have created a unit test in the JRuby project that reproduces this exact issue:
Axel-1@f22523d
Root Cause Analysis & Proposed Fix
Discussion context: This analysis was provided by the JRuby team during an email exchange regarding this issue.
The issue seems to stem from a cleanup mechanism introduced in PR #8483 (and updated in #8969). The goal was to clean up global state when thread-local embedded contexts terminate. However, the remove() method in GlobalVariable clears the variable from the runtime, which affects all threads sharing that runtime.
Basically, if a thread calls into the container and there are bound variables, that thread will remove those variables when it terminates; if the container is still in use, other threads will see the variable lost.
A temporary fix confirmed to work (and pass the reproduction test) is to disable the clearing of the global variable in org/jruby/embed/variable/GlobalVariable.java:
// core/src/main/java/org/jruby/embed/variable/GlobalVariable.java
@Override
public void remove() {
synchronized (getRuntime()) {
// The following line causes the issue in CONCURRENT mode:
// getRuntime().getGlobalVariables().clear(name);
}
}
Environment Information
9.4.14.0Linux x86_64,macOS 26.2 aarch64Description
We observed that when using
ScriptingContainerwithLocalContextScope.CONCURRENT, global variables are unexpectedly cleared or reset tonilunder concurrent load. The issue disappears completely when switching toLocalContextScope.SINGLETON.According to the documentation (https://github.com/jruby/jruby/wiki/RedBridge#user-content-Context_Instance_Type), global variables are not supposed to be thread-local in the embed API, yet they seem to be removed when a thread-local context terminates.
Expected Behavior
Global variables defined in the container should persist and remain accessible to other threads, even if the specific thread that accessed or modified them has terminated. The lifecycle of global variables should be bound to the container/runtime, not the individual thread context in
CONCURRENTmode.Actual Behavior
When threads calling into the container terminate, the cleanup process appears to be too aggressive and removes global variables from the associated JRuby runtime. Consequently, if the container is still in use, other active threads see these variables suddenly become
nil.Reproduction
I have created a unit test in the JRuby project that reproduces this exact issue:
Axel-1@f22523d
Root Cause Analysis & Proposed Fix
Discussion context: This analysis was provided by the JRuby team during an email exchange regarding this issue.
The issue seems to stem from a cleanup mechanism introduced in PR #8483 (and updated in #8969). The goal was to clean up global state when thread-local embedded contexts terminate. However, the
remove()method inGlobalVariableclears the variable from the runtime, which affects all threads sharing that runtime.Basically, if a thread calls into the container and there are bound variables, that thread will remove those variables when it terminates; if the container is still in use, other threads will see the variable lost.
A temporary fix confirmed to work (and pass the reproduction test) is to disable the clearing of the global variable in
org/jruby/embed/variable/GlobalVariable.java: