Skip to content

Global variable states lost when using LocalContextScope.CONCURRENT in Embed API #9234

Description

@Axel-1

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);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions