Class DefaultScriptEngineResolver
- All Implemented Interfaces:
ScriptEngineResolver
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprotected static classImmutable snapshot of GraalJS-relevant configuration flags captured at the time an engine is created and cached. -
Field Summary
FieldsModifier and TypeFieldDescriptionprotected final Map<String,ScriptEngine> protected final ScriptEngineManagerprotected final ThreadLocal<Map<String,DefaultScriptEngineResolver.GraalJsConfigSnapshot>> Stores the GraalJS configuration snapshot that was active when each thread-local engine was created.protected final ThreadLocal<Map<String,ScriptEngine>> Per-thread cache for script engines that enforce thread affinity (e.g. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidaddScriptEngineFactory(ScriptEngineFactory scriptEngineFactory) Captures the current GraalJS-relevant configuration into an immutable snapshot.voidReleases all thread-local cached engines and config snapshots for the calling thread.protected voidconfigureGraalJsScriptEngine(ScriptEngine scriptEngine) Allows providing custom configuration for the Graal JS script engine.protected voidconfigureGroovyScriptEngine(ScriptEngine scriptEngine) Allows providing custom configuration for the groovy script engine.protected voidconfigureScriptEngines(String language, ScriptEngine scriptEngine) protected ScriptEnginegetJavaScriptScriptEngine(String language) protected ScriptEnginegetScriptEngine(String language) getScriptEngine(String language, boolean resolveFromCache) Returns a cached script engine or creates a new script engine if no such engine is currently cached.protected ScriptEnginegetThreadLocalEngine(String language) Returns a thread-local cached engine for the given language, ornullif none is cached or the cache entry is stale.protected voidinvalidateThreadLocalEntry(String cacheKey) Removes a stale entry from both the engine cache and the config snapshot cache.protected booleanisCachable(ScriptEngine scriptEngine) Allows checking whether the script engine can be cached.protected booleanReturnstrueif the default JavaScript engine (ScriptingEngines.DEFAULT_JS_SCRIPTING_LANGUAGE, i.e.protected booleanisGraalJs(ScriptEngine scriptEngine) protected booleanisGraalJsConfigStale(String language, ProcessEngineConfigurationImpl config) Checks whether the GraalJS-specific configuration has changed since the engine was cached.protected booleanisJavaScriptAlias(String language) protected booleanisJsEngineNameStale(ScriptEngine cached, ProcessEngineConfigurationImpl config) Returnstrueif the cached engine's factory no longer matches the effective JavaScript engine name for the current configuration.protected booleanisThreadAffine(ScriptEngine scriptEngine) Returnstrueif the script engine enforces thread affinity (i.e.protected StringnormalizeLanguageKey(String language) Returns a canonical cache key for the given language.
-
Field Details
-
scriptEngineManager
-
cachedEngines
-
threadLocalEngines
Per-thread cache for script engines that enforce thread affinity (e.g. GraalJS). GraalJS binds its Polyglot Context to the creating thread and rejects eval() calls from any other thread. A global cache is therefore unsafe. Instead, each thread gets its own engine instance, reused across multiple script task executions on that thread. This bounds the number of live engines to the size of the thread pool (e.g. Tomcat's HTTP connector) and prevents the per-instance memory leak seen in GraalJS 25.x.Container lifecycle note: ThreadLocal entries are retained by the application server's worker threads. Call
clearThreadLocalResources()from each thread to release cached engines during engine shutdown / application undeploy. In typical Camunda deployments, the thread pool is destroyed together with the application, so this is only relevant for hot-redeploy scenarios.- See Also:
-
threadLocalConfigSnapshots
protected final ThreadLocal<Map<String,DefaultScriptEngineResolver.GraalJsConfigSnapshot>> threadLocalConfigSnapshotsStores the GraalJS configuration snapshot that was active when each thread-local engine was created. Used for staleness detection: if the current config no longer matches the snapshot, the cached engine must be discarded and recreated.We cannot read polyglot options back from the
ScriptContextbecause GraalJS consumes them when building its internal polyglot context. Instead, we record what was configured at creation time.
-
-
Constructor Details
-
DefaultScriptEngineResolver
-
-
Method Details
-
addScriptEngineFactory
- Specified by:
addScriptEngineFactoryin interfaceScriptEngineResolver
-
getScriptEngineManager
- Specified by:
getScriptEngineManagerin interfaceScriptEngineResolver
-
getScriptEngine
Returns a cached script engine or creates a new script engine if no such engine is currently cached.- Specified by:
getScriptEnginein interfaceScriptEngineResolver- Parameters:
language- the language (such as 'groovy' for the script engine)- Returns:
- the cached engine or null if no script engine can be created for the given language
-
getThreadLocalEngine
Returns a thread-local cached engine for the given language, ornullif none is cached or the cache entry is stale. A ThreadLocal entry is considered stale when:- The JavaScript engine name has been reconfigured (e.g. via
ProcessEngineConfiguration.setScriptEngineNameJavaScript()). - GraalJS-specific context options (
allowIO,allowHostAccess,nashorn-compat) no longer match the current process engine configuration. These options are baked into the polyglot context at creation time and cannot be changed afterwards, so the cached engine must be discarded.
- The JavaScript engine name has been reconfigured (e.g. via
-
invalidateThreadLocalEntry
Removes a stale entry from both the engine cache and the config snapshot cache. -
clearThreadLocalResources
public void clearThreadLocalResources()Releases all thread-local cached engines and config snapshots for the calling thread. This should be called during engine shutdown to prevent classloader leaks in container hot-redeploy scenarios where worker threads outlive the application.Since ThreadLocal values are per-thread, this method only clears the cache for the current thread. In application server environments, each worker thread that executed script tasks should call this method (e.g., via a shutdown listener or filter).
-
isJsEngineNameStale
Returnstrueif the cached engine's factory no longer matches the effective JavaScript engine name for the current configuration.The effective name is determined as follows:
- If
scriptEngineNameJavaScriptis explicitly configured, that name is used. - Otherwise, if the default JavaScript engine
(
ScriptingEngines.DEFAULT_JS_SCRIPTING_LANGUAGE, i.e. GraalJS) is available on the classpath, that is the effective name — a cached engine from a previous explicit configuration that has since been reset tonullis therefore correctly detected as stale. - Otherwise (default engine unavailable — fallback path in
getJavaScriptScriptEngine(java.lang.String)is in effect), the cached fallback engine is treated as not stale to prevent unnecessary recreation on every lookup.
If
configisnull(no process engine context available), the entry is conservatively treated as not stale, mirroring the pre-existing behavior for this edge case. - If
-
isDefaultJsEngineAvailable
protected boolean isDefaultJsEngineAvailable()Returnstrueif the default JavaScript engine (ScriptingEngines.DEFAULT_JS_SCRIPTING_LANGUAGE, i.e. GraalJS) appears in the JDKServiceLoader/SPI metadata already visible to theScriptEngineManager, without instantiating a new engine. Used byisJsEngineNameStale(javax.script.ScriptEngine, org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl)to correctly distinguish "no explicit engine configured, use the default" from "default unavailable, fallback is in effect". -
isGraalJsConfigStale
Checks whether the GraalJS-specific configuration has changed since the engine was cached. Compares the storedDefaultScriptEngineResolver.GraalJsConfigSnapshotagainst the current process engine configuration.We do NOT read polyglot attributes back from the ScriptContext because GraalJS consumes them when building its internal polyglot context. Instead, we compare against the snapshot recorded at creation time.
- Returns:
trueif the cached engine is stale and should be discarded.
-
captureGraalJsConfig
Captures the current GraalJS-relevant configuration into an immutable snapshot. -
isJavaScriptAlias
-
normalizeLanguageKey
Returns a canonical cache key for the given language. JavaScript aliases ("javascript","ecmascript") are normalized toScriptingEngines.JAVASCRIPT_SCRIPTING_LANGUAGEso that both aliases resolve to the same thread-local engine instance, keeping the intended bound of one GraalJS engine per thread. -
getScriptEngine
-
getJavaScriptScriptEngine
-
isCachable
Allows checking whether the script engine can be cached.GraalJS reports
THREADING = nullvia the JSR-223 factory parameter, which would normally prevent caching. However, GraalJS 25.x retains non-reclaimable native memory for every discardedScriptEngineinstance, causing a slow heap leak when the engine is re-created on every script task execution. Therefore GraalJS is explicitly allowed to be cached (per-thread, due to thread affinity) regardless of itsTHREADINGparameter value.- Parameters:
scriptEngine- the script engine to check.- Returns:
- true if the script engine may be cached.
-
isThreadAffine
Returnstrueif the script engine enforces thread affinity (i.e. its Polyglot Context is bound to the creating thread and rejects eval() from other threads). Such engines must be cached per-thread viathreadLocalEnginesinstead of in the globalcachedEnginesmap.Currently only GraalJS exhibits this behavior.
-
isGraalJs
-
configureScriptEngines
-
configureGroovyScriptEngine
Allows providing custom configuration for the groovy script engine.- Parameters:
scriptEngine- the groovy script engine to configure.
-
configureGraalJsScriptEngine
Allows providing custom configuration for the Graal JS script engine.- Parameters:
scriptEngine- the Graal JS script engine to configure.
-