You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by "Croway (via GitHub)" <gi...@apache.org> on 2026/09/11 08:43:36 UTC
[PR] CAMEL-24689: camel-javascript - share one GraalJS Engine, cache the Source, materialize results [camel]
Croway opened a new pull request, #26310:
URL: https://github.com/apache/camel/pull/26310
`JavaScriptHelper.newContext()` built a GraalJS `Context` without a shared `Engine`, so every evaluation created a private engine, re-parsed the script and allocated ~860 KB; the `Source` was rebuilt every time as well.
Following the camel-python3 pattern: `JavaScriptLanguage` becomes a `Service` owning one lazily created `Engine` (closed on stop); contexts are still created per evaluation, with `.engine(shared)` and the same access options as before; the `Source` is cached per script text; one INFO line at engine creation reports interpreted mode (the `engine.WarnInterpreterOnly` option moved to the engine builder, same value); JS objects, arrays, `Map`, `Set` and `Date` returned by a script are materialized before the `Context` closes (on main `o.as(Object.class)` returned proxies bound to a closed context, which threw on first access).
**Measured.** Expressions 1.8× faster, 1-thread route 1.8×, 8-thread 5-minute load 4,697 → 15,006 msg/s (3.2×) with 1.77 MB → 323 KB per message. One reading to look at: the 8-thread JMH route read 0.67× with the shared engine (p99 2.7 → 4.8 ms), so the shared engine serialises something under concurrency in interpreted mode. 14 tests (9 new: concurrency, stop/start, two contexts, result materialization).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
_Claude Code on behalf of Croway_
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-24689: camel-javascript - share one GraalJS Engine, cache the Source, materialize results [camel]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26310:
URL: https://github.com/apache/camel/pull/26310#issuecomment-5632239553
<!-- ci-tested-modules -->
:test_tube: **CI tested the following changed modules:**
- `components/camel-javascript`
---
<details><summary>:microscope: Scalpel shadow comparison — Scalpel: 9 tested, 27 compile-only — current: 9 all tested</summary>
[Maveniverse Scalpel](https://github.com/maveniverse/scalpel) detected **36 affected modules** (current approach: 9).
<details><summary>:warning: Modules only in Scalpel (27)</summary>
- `apache-camel`
- `camel-allcomponents`
- `camel-catalog`
- `camel-catalog-console`
- `camel-catalog-maven`
- `camel-catalog-suggest`
- `camel-componentdsl`
- `camel-endpointdsl`
- `camel-endpointdsl-support`
- `camel-itest`
- `camel-jbang-core`
- `camel-jbang-it`
- `camel-jbang-main`
- `camel-jbang-plugin-edit`
- `camel-jbang-plugin-generate`
- `camel-jbang-plugin-kubernetes`
- `camel-jbang-plugin-test`
- `camel-kamelet-main`
- `camel-launcher`
- `camel-report-maven-plugin`
- `camel-route-parser`
- `camel-yaml-dsl`
- `camel-yaml-dsl-deserializers`
- `camel-yaml-dsl-maven-plugin`
- `coverage`
- `docs`
- `dummy-component`
</details>
**Skip-tests mode would test 9 modules** (1 direct + 8 downstream), **skip tests for 27** (generated code, meta-modules)
<details><summary>Modules Scalpel would test (9)</summary>
- `camel-javascript`
- `camel-jbang-mcp`
- `camel-jbang-plugin-mcp`
- `camel-jbang-plugin-route-parser`
- `camel-jbang-plugin-tui`
- `camel-jbang-plugin-validate`
- `camel-launcher-container`
- `camel-yaml-dsl-validator`
- `camel-yaml-dsl-validator-maven-plugin`
</details>
<details><summary>Modules with tests skipped (27)</summary>
- `apache-camel`
- `camel-allcomponents`
- `camel-catalog`
- `camel-catalog-console`
- `camel-catalog-maven`
- `camel-catalog-suggest`
- `camel-componentdsl`
- `camel-endpointdsl`
- `camel-endpointdsl-support`
- `camel-itest`
- `camel-jbang-core`
- `camel-jbang-it`
- `camel-jbang-main`
- `camel-jbang-plugin-edit`
- `camel-jbang-plugin-generate`
- `camel-jbang-plugin-kubernetes`
- `camel-jbang-plugin-test`
- `camel-kamelet-main`
- `camel-launcher`
- `camel-report-maven-plugin`
- `camel-route-parser`
- `camel-yaml-dsl`
- `camel-yaml-dsl-deserializers`
- `camel-yaml-dsl-maven-plugin`
- `coverage`
- `docs`
- `dummy-component`
</details>
> :information_source: Shadow mode — Scalpel observes but does not affect test execution. [Learn more](https://github.com/maveniverse/scalpel)
</details>
<details><summary>All tested modules (36 modules, 5m 9s total)</summary>
**Total reactor time:** 5m 9s
| Module | Duration | Status |
| --- | --- | --- |
| Camel :: Launcher | 49.2s | SUCCESS |
| Camel :: JBang :: Plugin :: TUI | 41.9s | SUCCESS |
| Camel :: JBang :: MCP | 41.9s | SUCCESS |
| Camel :: Component DSL | 24.7s | SUCCESS |
| Camel :: Catalog :: Camel Catalog | 21.8s | SUCCESS |
| Camel :: JBang :: Plugin :: Kubernetes | 18.5s | SUCCESS |
| Camel :: Docs | 16.6s | SUCCESS |
| Camel :: JavaScript | 11.8s | SUCCESS |
| Camel :: JBang :: Plugin :: Testing | 9.8s | SUCCESS |
| Camel :: Catalog :: Camel Route Parser | 9.2s | SUCCESS |
| Camel :: Kamelet Main | 9.1s | SUCCESS |
| Camel :: Catalog :: Camel Report Maven Plugin | 9.0s | SUCCESS |
| Camel :: YAML DSL :: Deserializers | 6.9s | SUCCESS |
| Camel :: All Components Sync point | 5.8s | SUCCESS |
| Camel :: YAML DSL :: Validator | 5.0s | SUCCESS |
| Camel :: YAML DSL :: Maven Plugins | 4.2s | SUCCESS |
| Camel :: Catalog :: Maven | 3.5s | SUCCESS |
| Camel :: YAML DSL :: Validator Maven Plugin | 2.9s | SUCCESS |
| Camel :: Catalog :: Suggest (deprecated) | 2.5s | SUCCESS |
| Camel :: Catalog :: Dummy Component | 2.0s | SUCCESS |
| Camel :: Assembly | 1.7s | SUCCESS |
| Camel :: Endpoint DSL :: Support | 1.5s | SUCCESS |
| Camel :: JBang :: Plugin :: Generate | 1.2s | SUCCESS |
| Camel :: JBang :: Plugin :: Edit | 1.2s | SUCCESS |
| Camel :: Coverage | 1.2s | SUCCESS |
| Camel :: JBang :: Integration tests | 1.1s | SUCCESS |
| Camel :: Catalog :: Console | 1.0s | SUCCESS |
| Camel :: JBang :: Plugin :: Validate | 0.8s | SUCCESS |
| Camel :: JBang :: Main | 0.8s | SUCCESS |
| Camel :: Launcher :: Container | 0.7s | SUCCESS |
| Camel :: JBang :: Plugin :: MCP | 0.7s | SUCCESS |
| Camel :: JBang :: Plugin :: Route Parser | 0.6s | SUCCESS |
| Camel :: Endpoint DSL | n/a | |
| Camel :: Integration Tests | n/a | |
| Camel :: JBang :: Core | n/a | |
| Camel :: YAML DSL | n/a | |
**Top 20 slowest modules:**
- `Camel :: Launcher` (49.2s)
- `Camel :: JBang :: Plugin :: TUI` (41.9s)
- `Camel :: JBang :: MCP` (41.9s)
- `Camel :: Component DSL` (24.7s)
- `Camel :: Catalog :: Camel Catalog` (21.8s)
- `Camel :: JBang :: Plugin :: Kubernetes` (18.5s)
- `Camel :: Docs` (16.6s)
- `Camel :: JavaScript` (11.8s)
- `Camel :: JBang :: Plugin :: Testing` (9.8s)
- `Camel :: Catalog :: Camel Route Parser` (9.2s)
- `Camel :: Kamelet Main` (9.1s)
- `Camel :: Catalog :: Camel Report Maven Plugin` (9.0s)
- `Camel :: YAML DSL :: Deserializers` (6.9s)
- `Camel :: All Components Sync point` (5.8s)
- `Camel :: YAML DSL :: Validator` (5.0s)
- `Camel :: YAML DSL :: Maven Plugins` (4.2s)
- `Camel :: Catalog :: Maven` (3.5s)
- `Camel :: YAML DSL :: Validator Maven Plugin` (2.9s)
- `Camel :: Catalog :: Suggest (deprecated)` (2.5s)
- `Camel :: Catalog :: Dummy Component` (2.0s)
</details>
---
:gear: [View full build and test results](https://github.com/apache/camel/actions/runs/34580588839)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-24689: camel-javascript - share one GraalJS Engine, cache the Source, materialize results [camel]
Posted by "davsclaus (via GitHub)" <gi...@apache.org>.
davsclaus commented on code in PR #26310:
URL: https://github.com/apache/camel/pull/26310#discussion_r3989778047
##########
components/camel-javascript/src/main/java/org/apache/camel/language/js/JavaScriptLanguage.java:
##########
@@ -16,21 +16,62 @@
*/
package org.apache.camel.language.js;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
+import org.apache.camel.Service;
import org.apache.camel.spi.ScriptingLanguage;
import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.LRUCacheFactory;
import org.apache.camel.support.TypedLanguageSupport;
import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
-import static org.graalvm.polyglot.Source.newBuilder;
-
+/**
+ * Camel expression language for JavaScript via <a href="https://www.graalvm.org/javascript/">GraalJS</a>.
+ * <p>
+ * One {@link Engine} is shared by all evaluations of a language instance so parsed and compiled scripts are reused; a
+ * fresh {@link Context} is still created per evaluation so scripts stay isolated from each other. The engine is created
Review Comment:
"The engine is created lazily on first use" is not what happens. `start()` calls `engine()` unconditionally, and `AbstractCamelContext.doResolveLanguage(...)` starts a `Language` as soon as it is resolved:
```java
if (language instanceof Service service) {
CamelContextAware.trySetCamelContext(service, camelContext);
ServiceHelper.initService(service);
startService(service);
}
```
So the `Engine` is built the moment anything resolves the `js` language — during route startup, not on first evaluation. That is probably what you want (pay the GraalJS engine-build cost at startup rather than on the first message), but the comment should say so, since "lazily on first use" sets a startup-cost expectation that is not met.
Either drop the `engine()` call from `start()` and keep the sentence, or keep `start()` and reword to something like "created when the language is started and closed when it is stopped; the `engine()` accessor also builds it on demand for expressions used before start".
##########
components/camel-javascript/src/main/java/org/apache/camel/language/js/JavaScriptLanguage.java:
##########
@@ -45,14 +86,140 @@ public Expression createExpression(String expression) {
@Override
public <T> T evaluate(String script, Map<String, Object> bindings, Class<T> resultType) {
script = loadResource(script);
- try (Context cx = JavaScriptHelper.newContext()) {
- Value b = cx.getBindings("js");
- bindings.forEach(b::putMember);
- Source source = newBuilder("js", script, "Unnamed")
- .mimeType("application/javascript+module").buildLiteral();
- Value o = cx.eval(source);
- Object answer = o != null ? o.as(resultType) : null;
- return resultType.cast(answer);
+ try (Context cx = newContext()) {
+ if (bindings != null) {
+ Value b = cx.getBindings("js");
+ bindings.forEach(b::putMember);
+ }
+ Value o = cx.eval(source(script));
+ Object answer = materialize(o);
+ if (answer == null || resultType == Object.class || resultType.isInstance(answer)) {
+ return resultType.cast(answer);
+ }
+ if (getCamelContext() != null) {
+ return getCamelContext().getTypeConverter().convertTo(resultType, answer);
Review Comment:
`TypeConverter.convertTo(...)` returns `null` when it has no converter for the pair, rather than throwing. Previously this path was `o.as(resultType)`, which raised a `ClassCastException`/`PolyglotException` on an impossible conversion.
So a `ScriptingLanguage` caller asking for a type the materialized value cannot become now silently gets `null` instead of an error pointing at the script. `mandatoryConvertTo(...)` keeps the old "fail loudly" contract and gives a much better message than the polyglot exception did:
```suggestion
return getCamelContext().getTypeConverter().mandatoryConvertTo(resultType, answer);
```
If a lenient `null` is deliberate here, worth a short comment saying so — it is the kind of thing that reads as an oversight later.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-24689: camel-javascript - share one GraalJS Engine, cache the Source, materialize results [camel]
Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on PR #26310:
URL: https://github.com/apache/camel/pull/26310#issuecomment-5631823148
:star2: Thank you for your contribution to the Apache Camel project! :star2:
:robot: CI automation will test this PR automatically.
:camel: Apache Camel Committers, please review the following items:
* First-time contributors **require MANUAL approval** for the GitHub Actions to run
* You can use the command `/component-test (camel-)component-name1 (camel-)component-name2..` to request a test from the test bot although they are normally detected and executed by CI.
* You can label PRs using `skip-tests` and `test-dependents` to fine-tune the checks executed by this PR.
* Build and test logs are available in the summary page. **Only** [Apache Camel committers](https://camel.apache.org/community/team/#committers) have access to the summary.
:warning: Be careful when sharing logs. Review their contents before sharing them publicly.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-24689: camel-javascript - share one GraalJS Engine, cache the Source, materialize results [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on PR #26310:
URL: https://github.com/apache/camel/pull/26310#issuecomment-5636381007
Thanks, all addressed in 50f185334d07: a `=== camel-javascript` upgrade-guide entry covering the materialized return types (LinkedHashMap, ArrayList, LinkedHashSet, Instant instead of a Value bound to a closed Context) and the deprecated newContext(); javadoc reworded for the eager engine build; mandatoryConvertTo on the generic evaluate; the Set detection documented as a heuristic on the meta object name; stopOnEviction=false on the Source cache. 14 tests green.
_Claude Code on behalf of Croway_
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-24689: camel-javascript - share one GraalJS Engine, cache the Source, materialize results [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway merged PR #26310:
URL: https://github.com/apache/camel/pull/26310
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] CAMEL-24689: camel-javascript - share one GraalJS Engine, cache the Source, materialize results [camel]
Posted by "Croway (via GitHub)" <gi...@apache.org>.
Croway commented on code in PR #26310:
URL: https://github.com/apache/camel/pull/26310#discussion_r3990431965
##########
components/camel-javascript/src/main/java/org/apache/camel/language/js/JavaScriptLanguage.java:
##########
@@ -16,21 +16,62 @@
*/
package org.apache.camel.language.js;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
+import org.apache.camel.Service;
import org.apache.camel.spi.ScriptingLanguage;
import org.apache.camel.spi.annotations.Language;
+import org.apache.camel.support.LRUCacheFactory;
import org.apache.camel.support.TypedLanguageSupport;
import org.graalvm.polyglot.Context;
+import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
-import static org.graalvm.polyglot.Source.newBuilder;
-
+/**
+ * Camel expression language for JavaScript via <a href="https://www.graalvm.org/javascript/">GraalJS</a>.
+ * <p>
+ * One {@link Engine} is shared by all evaluations of a language instance so parsed and compiled scripts are reused; a
+ * fresh {@link Context} is still created per evaluation so scripts stay isolated from each other. The engine is created
Review Comment:
Applied in 50f185334d07: kept the eager build in start() (paying the engine cost at route startup is the intent) and reworded the javadoc to say so, with the on-demand build noted for expressions used before start.
_Claude Code on behalf of Croway_
##########
components/camel-javascript/src/main/java/org/apache/camel/language/js/JavaScriptLanguage.java:
##########
@@ -45,14 +86,140 @@ public Expression createExpression(String expression) {
@Override
public <T> T evaluate(String script, Map<String, Object> bindings, Class<T> resultType) {
script = loadResource(script);
- try (Context cx = JavaScriptHelper.newContext()) {
- Value b = cx.getBindings("js");
- bindings.forEach(b::putMember);
- Source source = newBuilder("js", script, "Unnamed")
- .mimeType("application/javascript+module").buildLiteral();
- Value o = cx.eval(source);
- Object answer = o != null ? o.as(resultType) : null;
- return resultType.cast(answer);
+ try (Context cx = newContext()) {
+ if (bindings != null) {
+ Value b = cx.getBindings("js");
+ bindings.forEach(b::putMember);
+ }
+ Value o = cx.eval(source(script));
+ Object answer = materialize(o);
+ if (answer == null || resultType == Object.class || resultType.isInstance(answer)) {
+ return resultType.cast(answer);
+ }
+ if (getCamelContext() != null) {
+ return getCamelContext().getTypeConverter().convertTo(resultType, answer);
Review Comment:
Applied in 50f185334d07: mandatoryConvertTo, wrapped as a RuntimeCamelException, so an impossible conversion fails loudly as before.
_Claude Code on behalf of Croway_
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@camel.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org