[SCXML-290] Make JSEvaluator.initGlobalsScript volatile - #396
Open
akashchamp wants to merge 1 commit into
Open
akashchamp wants to merge 1 commit into
akashchamp wants to merge 1 commit into
Conversation
JSEvaluator#initGlobalsScript is a private static String that caches the contents of the init_global.js classpath resource. It is written exactly once, inside the per-instance synchronized initEngine() method, but is read without any synchronization from getScriptContext() (and by every other JSEvaluator instance's initEngine() null-check). Because initEngine() synchronizes on the individual JSEvaluator instance (this), and separate instances are commonly constructed concurrently (one per SCXML instance), the write to the static field and a read of it on another instance do not share a common monitor. The JMM therefore does not guarantee that a thread will observe the script content published by another thread's initEngine() call; it may observe a stale or null value. Marking the field volatile establishes the required happens-before edge between any write and any subsequent read, independent of which instance or monitor performed the write, fixing the visibility gap directly at its source. Also adds a JSEvaluatorTest that constructs and evaluates many JSEvaluator instances concurrently, exercising the previously-racy initialization path under real thread contention. Generated-by: Claude Sonnet 5 -- https://www.apache.org/legal/generative-tooling.html Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes SCXML-290: https://issues.apache.org/jira/browse/SCXML-290
Problem
JSEvaluator#initGlobalsScriptis aprivate static Stringthat cachesthe contents of the
init_global.jsclasspath resource. It is writtenexactly once, inside the per-instance
synchronized initEngine()method:but it is read without any synchronization elsewhere, e.g. in
getScriptContext():initEngine()synchronizes on the individualJSEvaluatorinstance(
this). SeparateJSEvaluatorinstances (one per SCXML instance, perthe class's own Javadoc) are routinely constructed and used concurrently
by different threads, so the write on one instance's monitor and a read
via another instance's
getScriptContext()/initEngine()do not share acommon monitor. The Java Memory Model therefore does not guarantee that a
thread will observe the script content published by another thread's
initEngine()call, and may see a stale ornullvalue.Fix
Mark
initGlobalsScriptvolatile. This establishes the requiredhappens-before edge between any write and any subsequent read of the
field, regardless of which instance or monitor performed the write,
closing the visibility gap directly at its source without changing the
existing per-instance locking structure.
Tests
Added
JSEvaluatorTest#testConcurrentEvaluatorInitialization, whichconstructs and evaluates 32
JSEvaluatorinstances concurrently from athread pool, exercising the previously-racy lazy-initialization path
under real thread contention and failing loudly on any exception or
incorrect result.
Note: a Java Memory Model visibility bug like this one is inherently
non-deterministic to reproduce as a hard test failure in a single JVM run
(it depends on the JIT, CPU cache behavior, and scheduling, none of which
a portable unit test controls). The added test provides concurrency
coverage of the affected code path but cannot itself prove the pre-fix
race; the fix is otherwise a direct, minimal application of the standard
JMM remedy for this exact pattern (write under one lock, read without a
shared lock).
Verification
mvn clean verify— BUILD SUCCESS, 241 tests run, 0 failures, 0 errors, 0 skipped (includes RAT license check and Jacoco coverage check).mvn test -Dtest=JSEvaluatorTest— 12/12 tests pass (11 existing + 1 new).mvn; that'smvnon the command line by itself.