Repository navigation
[ISSUE #7561] Add reusable request, configuration and result bridge for wasm base plugins. - #7569
yangjj-iso wants to merge 4 commits into
Conversation
wy471x
left a comment
There was a problem hiding this comment.
Large but well-scoped bridge. I built and ran the affected reactor locally on this head (JDK 21):
wasm-api:AbstractWasmPluginTest2/2,WasmLoaderTest1/1.wasm-base:AbstractShenyuWasmPluginTest7/7,WasmBridgeTest15/15,WasmRequestBridgeTest38/38,AbstractWasmPluginDataHandlerTest12/12,AbstractWasmDiscoveryHandlerTest2/2,AbstractWasmMetaDataHandlerTest2/2 — cumulative 76/76, no failures; checkstyle 0 violations;apache-rat:check0 unapproved on both modules.- The protocol is defensive in the right places: strict UTF-8/JSON (duplicate keys and trailing documents rejected), 1 MiB cap enforced before allocation,
long→intnarrowing guarded bycheckRange, framing / hop-by-hop headers refused, header names validated against the token grammar with case-insensitive duplicate detection, the result copied out of guest memory before the lock is released, invocation ids expired infinally, andget_args(id, 0, 0)size negotiation that writes nothing on an undersized buffer.
Coordination issue with the sibling WASM PRs (same base, same author). WasmLoader.getWasmExtern here is a third variant of the change also made by #7567 (draft) and #7568. This PR filters on ExternalType.FUNCTION and returns empty for a non-function export; #7568/#7567 let instance.export(...) throw InvalidException for that case. The expectations are mutually exclusive — I applied this PR and ran #7568's nonFunctionExportStillFailsTest: Expected com.dylibso.chicory.wasm.InvalidException to be thrown, but nothing was thrown. Whichever of the three merges second also conflicts on the same hunk. Please settle one lookup policy and land it once (a shared findWasmExtern helper in #7567, with #7568/#7569 rebased on top), and drop the duplicate here.
Behaviour-change notes that deserve a line in the description / a migration note, since AbstractShenyuWasmPlugin is a public base class:
- The no-argument constructor now installs the JSON bridge instead of the built-in no-op
get_args/put_result. An external subclass that used the no-arg constructor now gets bridge semantics — strict JSON result parsing, 1 MiB cap, fail-closed HTTP 500 for a result the bridge cannot parse — rather than the previous no-op callbacks. There is no in-repo production subclass, but this is exactly the kind of change that needs to be called out. close()only runs if the Spring bean is registered with a destroy method; the README says so, but no starter wiring is added here, so the "idempotent shutdown cleanup" remains opt-in.- The default 5-arg
doExecutenow continues the chain andgetArgumentIdis concrete. That is source/binary compatible for existing subclasses, and theConsumer<Store>constructor still preserves the eager, custom-adapter path (covered bycustomAdapterPreservesEagerInvocationTest), so this is fine — just note that the two constructors now mean "bridge" vs "custom adapter", not "default host functions" vs "custom host functions".
Notes (non-blocking):
synchronized (wasmLoader)serialises every guest call for one plugin instance, hooks included. That is the safe choice for shared guest linear memory; a short comment on why the lock is on the loader (shared across the plugin, not per-bridge) would prevent it from being "optimised" later.WasmResult.applymutates the response headers before the continuation runs; if the downstream chain then fails, the header changes stay applied. Probably intended, but worth a comment.customAdapterPreservesEagerInvocationTestproves eagerness through the mocked chain interaction; asserting the guest itself was entered (e.g. a counter in the fixture) would make the intent sharper.
Aias00
left a comment
There was a problem hiding this comment.
Approve. This adds a reusable, well-designed JSON request/configuration/result bridge for WASM plugins, and I focused my review on the security and correctness of the bridge.
Key points verified in the diff:
- Export lookup correctness.
WasmLoader.getWasmExternnow iterates the moduleExportSectionand only returns a function whenexport.exportType() == ExternalType.FUNCTION. This fixes a real bug where a non-function export likememorywould have been treated as a callable (or thrown). A newWasmLoaderTestcovers present/absent/non-function cases. - Strong result validation.
WasmResult.parseenforces: strict UTF-8, JSON object withversion == 1, a closed set of fields,action ∈ {CONTINUE, REJECT},REJECTrequires an HTTP status in 400–599 and cannot set request headers,CONTINUEcannot set status/body, header names are validated (no framing headers such asHost/Content-Length, valid token pattern, unique, no control characters). Jackson is configured withFAIL_ON_TRAILING_TOKENSandSTRICT_DUPLICATE_DETECTION. - Lifecycle safety.
AbstractShenyuWasmPluginnow implementsAutoCloseable; the bridge serializes guest-memory access viasynchronized (wasmLoader), andclose()is idempotent and refuses further invocations.begin/end/closeclear argument/result state, avoiding leaks. - Backward compatibility. The
Consumer<Store>constructor retains custom host adapters (bridge = null), and the legacy path is unchanged; existing custom-constructor Go/Rust tests remain as regression coverage.
Tests (customAdapterPreservesEagerInvocationTest, plus the referenced WasmBridgeTest/WasmRequestBridgeTest) exercise both the supported bridge and the legacy adapter, and the README documents the protocol, limits and fixture regeneration clearly.
Non-blocking: the README is extensive; please make sure the referenced CI command and wat2wasm fixture-regeneration steps are wired into the build/CI so the committed .wasm fixtures stay reproducible.
|
@wy471x I addressed the coordination and compatibility points in 1fc74a2, synced with master at a89459b. This PR now uses the strict lookup policy already merged in #7568: missing exports return empty, while a same-named non-function export throws InvalidException. There is no WasmLoader production change left in this diff, and the lookup test agrees with #7568's regression. #7567 retains the shared private helper only for consistent initializer validation; this bridge PR no longer duplicates a competing lookup implementation. The README and release note explicitly describe the no-argument constructor's new JSON semantics, strict parsing/1 MiB/HTTP 500 behavior, the explicit-null constructor path for retaining no-op callbacks, custom-adapter compatibility, concrete continuation/ID defaults, and opt-in destruction wiring. I added comments explaining the loader lock and response-header behavior, and strengthened the eager-adapter regression to assert that the guest's put_result callback was entered before subscribing. The new fixture CI workflow pins WABT 1.0.42 and Rust 1.90.0. Local verification reproduced all three WAT binaries exactly, rebuilt the five Rust guests and passed 89 WASM tests against them, with zero failures/errors/skips. An intentionally corrupted WAT binary is rejected by the verifier. Checkstyle, Apache RAT and the pinned license checker pass. The two sibling branches have no current conflicts with master; new CI is running. |
Aias00
left a comment
There was a problem hiding this comment.
Summary
Fresh review of the current head 0b0f780fb02c8b65cdbf15b98764a9f430dac1ae (open, not draft, author yangjj-iso). Fixes/Closes #7561. Adds a reusable request/configuration/result bridge to AbstractShenyuWasmPlugin, plus shared fixture-verification hardening and E2E CI regression repairs.
What the change does
AbstractShenyuWasmPluginno-arg constructor now installs the JSON bridge (AbstractShenyuWasmPlugin.java:77), while theConsumer<Store>constructor keeps custom host adapters (:90).doExecute(..., Long)andgetArgumentId(...)are no longer abstract; they are concrete with a default chain-continuation body (:126,:198). The class now implementsAutoCloseable(:56,close()at:232). Bridge execution serializes on the loader and expires state infinally(:147–:169).WasmRequestBridge(new) registersshenyu.get_args/shenyu.put_result, emits a versioned UTF-8 JSON snapshot of request metadata, cachedPluginDataand matchedSelectorData/RuleData, caps payloads at 1 MiB and validates buffer ranges and single-submit (WasmRequestBridge.java:87–:129).WasmResult(new) validates strict JSON (FAIL_ON_TRAILING_TOKENS+STRICT_DUPLICATE_DETECTION), version 1, a field whitelist,CONTINUE/REJECTsemantics,400–599reject status, UTF-8 bodies and header names/values, and rejects framing headers (WasmResult.java:59–:163). Invalid results fail closed with HTTP 500.- E2E/shared-infra: local
httpbinfixture wired into six consuming Compose files with a healthcheck (compose/fixtures/httpbin.py,httpbin.yml), RocketMQ access-log consumption switched to a lite-pull consumer with topic-creation waiting (DividePluginCases.java:87), Spring Cloud route-readiness wait, andWaitForHelperTestrewritten against an ephemeral localHttpServerinstead of postman-echo. - CI hardening:
verify-wasm-fixtures.pynow enumerateswasm-*families and requireswasm-gateway/wasm-concurrent; newtest-wasm-fixtures.pycovers failure cleanup/restoration.
Verification
- Compared against
master: master'sAbstractShenyuWasmPluginhas no bridge (no-arg constructor used a null initializer → built-in no-opget_args/put_result), anddoExecute(..., Long)/getArgumentIdwereabstract. Removingabstractis source- and binary-compatible for subclasses; the in-repo subclasses (ReferenceWasmPlugin,WasmTestPlugin) use theConsumer<Store>constructor and continue on the legacy path.WasmLoader.javais unchanged by this PR. - Local:
mvn -pl shenyu-plugin/shenyu-plugin-wasm-base -am test→ BUILD SUCCESS. wasm-api 13/13 (AbstractWasmPluginTest4,WasmLoaderTest1,WasmLoaderConcurrentTest8); wasm-base 97/97 (WasmBridgeTest15,WasmRequestBridgeTest38,AbstractShenyuWasmPluginTest7,WasmGatewayTest9,WasmLifecycleContractTest4, handlers 24). Zero failures/errors/skips. - CI: all relevant checks green —
pr_build,rebuild-and-test,Analyze (java),check-license-header,rat_check,scope,sql-matrix,registry_dependencies,agent_gateway_migrations,k8s-examples-http,build-docker-images, and everye2e-case/e2e-storage. No red to attribute.
Notes (non-blocking)
- The no-arg constructor is a documented behavior change for external subclasses that relied on no-op callbacks; the
super((Consumer<Store>) null)escape hatch and RELEASE-NOTES/README cover it. Acceptable for a minor release but worth calling out in the changelog (already done). - The PR is broad: it bundles the #7561 bridge with E2E CI repairs and fixture-verifier hardening (shared with #7581). All parts were verified independently here and CI is green; merging #7581 first as the description requests keeps the shared files tidy.
close()/AutoCloseablerequires explicit destroy wiring (documented); no starter wiring is added, which matches the stated scope.
Conclusion
APPROVE. The bridge contract is strict and fail-closed, the legacy adapter path is preserved, the behavior change is documented with an escape hatch, and both CI and local module tests pass.
Aias00
left a comment
There was a problem hiding this comment.
Re-approving. The only commit after my last review is 4f652e05, and it is a merge of master (4d33568d) into the branch — two parents, no hand-written changes on top of 0b0f780f. So the code is exactly what I reviewed and approved; the approval was dismissed by the push, not by a code change.
CI on 4f652e05 is green for everything that runs on this PR: 17 pass, 10 skipping, 0 pending, 0 fail.
One thing to be aware of when it lands: the merge brought in 251 files of master movement, so the squash commit will be large and the diff against master is no longer a clean representation of this PR's intent. If a maintainer wants a readable history entry, the PR description should carry the summary rather than relying on the commit diff.
|
Heads-up: this branch now has merge conflicts with The conflict is around the wasm bridge surface you introduce ( Please rebase onto |
4f652e0 to
f54d215
Compare
c6b9992 to
9db8352
Compare
Aias00
left a comment
There was a problem hiding this comment.
Approved. The reusable JSON request/configuration/result bridge is well designed and security-conscious. WasmRequestBridge uses the standard grow-buffer get_args protocol, bounds-checks the guest address/length against memory and the 1 MiB payload cap, and rejects expired or unknown invocation IDs. WasmResult performs strict JSON parsing (fails on trailing tokens, duplicate keys, malformed UTF-8), requires version 1, restricts action to CONTINUE/REJECT, requires a 400-599 status on REJECT, forbids request-header changes on reject, blocklists framing/hop-by-hop headers (Host, Content-Length, Connection, ...) and control characters, and applies request-header changes through an immutable decorator. The behavioral change (no-arg constructor now selects the JSON bridge) is documented in RELEASE-NOTES with a migration path for subclasses needing the old no-op callbacks, and the protocol is fully documented in the module README. Tests cover the flow with real WASM fixtures. Nice work.
|
Hi @yangjj-iso, this PR was approved, but the squash-merge could not be completed because it now has merge conflicts ( The conflict is in the WASM area — sibling WASM PR #7948 (WasmExecutionLimits / Please rebase your branch onto the latest |
9db8352 to
fcd87b4
Compare
Aias00
left a comment
There was a problem hiding this comment.
Reviewed the reusable Wasm request/configuration/result bridge and its interaction with the plugin execution path. No actionable finding.
Validation: 63 focused bridge/plugin/loader tests passed. The PR currently has merge conflicts; conflict resolution needs revalidation. Apache RAT passed locally. Scope: source review and targeted verification; full integration/e2e suites were not run locally for this review.
2345354 to
fca5896
Compare
Aias00
left a comment
There was a problem hiding this comment.
Re-reviewed the current integration and confirmed the previously reviewed Wasm bridge production logic is unchanged. Focused bridge/plugin/loader tests: 63 tests passed. No actionable finding. The sampled failing registry_dependencies CI job hits the existing Admin constructor-fixture mismatch tracked by #8247; required CI must be green before merging.
Checkstyle and a separate Apache RAT check passed locally. This review covers source and the stated targeted verification; full Docker integration/E2E suites were not run locally.
63 Wasm bridge and loader tests passed; Checkstyle and RAT passed. Signed-off-by: redyyang <[email protected]>
Aias00
left a comment
There was a problem hiding this comment.
Stream Agent analysis
FINAL synthesis of PR #7569 (head 85d468a) from 7 partition reviews: part-0 docs PASS, part-1 test PASS, part-2 docs PASS, part-3 impl PASS, part-4 tests PASS, part-5 binary .wasm resources INCONCLUSIVE (no diff hunks), part-6 WAT/Makefile PASS. all_parts_passed false, complete_diff false, CI not passed. Verdict INCONCLUSIVE due to incomplete evidence.
Evidence
- Part-5 review node: INCONCLUSIVE; added binary .wasm resources with available:false, additions:0/deletions:0, no diff hunks (frozen attachment).
- CI object: check_count 10, complete true, passed false, legacy_state pending.
- complete_diff false, global_complete_diff false in frozen cross-file review attachment.
Recommended next steps
- Provide complete diff hunks for the added binary .wasm resources (or confirm they are generated from WAT sources in part-6).
- Ensure all CI checks pass (currently ci.passed=false) before final approval.
- Re-run FINAL review after complete_diff true and all partition uncertainties resolved.
Scope: captured diff and GitHub check metadata only; no local tests were executed.
Task: a70bc237-a2c3-4c31-bcfe-3348d29b3b67 · Commit: 85d468a095863d5d25e208146f5a827658ba0c47
Review verdict: INCONCLUSIVE
SYNTHESIS/FINAL scope: child part-5 returned INCONCLUSIVE due to missing/binary patch (no diff hunks available), making all_parts_passed false. complete_diff false and CI passed false. Rules forbid overriding INCONCLUSIVE with PASS and require all_parts_passed true, complete_diff true, CI passed for FINAL PASS. No HIGH/CRITICAL findings anchored in any child; uncertainty preserved through INCONCLUSIVE verdict.
Publication: COMMENT (INCONCLUSIVE_REVIEW)
Review details
This PR adds a reusable WASM JSON bridge. Partition reviews found no concrete blocking defects in supplied code/test/doc segments, but partition part-5 (added binary .wasm test resources) had no available diff hunks and is INCONCLUSIVE. Moreover complete_diff is false and CI is failing/pending. Therefore the final verdict is INCONCLUSIVE; please supply full diff including binary resource details and ensure CI passes before a complete review can be finalized.
|
Follow-up on the binary-fixture evidence requested in the latest review of The job installs the pinned WABT toolchain, rebuilds the fixtures (including |
Closes #7561.
Provide a versioned UTF-8 JSON request/configuration/result bridge for AbstractShenyuWasmPlugin. Guests can continue with validated header updates or reject with HTTP 400–599. Strict UTF-8/JSON, closed result fields, header/buffer validation and a 1 MiB payload bound reject invalid results with HTTP 500.
Compatibility: the no-argument constructor now selects the JSON bridge. Existing subclasses that need no-op host callbacks can use super((Consumer) null); custom adapters retain their eager behavior when execution deadlines are disabled. The default Java continuation and argument-ID implementation remain overridable. Shutdown requires explicit destroy wiring or close(), as documented.
Preserve the shared loader's merged execution limits, bounded worker/cancellation behavior and memory ceilings. Deadline-enabled execution is deferred and offloaded from the calling event loop. Synchronize host callbacks with invocation retirement so a deadline cannot clear/reuse the bridge's shared slot while its guest worker copies a result. Export lookup uses master's existing strict non-function policy; this PR does not add another WasmLoader implementation.
Review follow-up on 2026-11-02: synchronized the published branch with master 92d16c2. The reviewed bridge production logic is unchanged; the current diff contains only bridge code, documentation and its tests/fixtures. Upstream release-note entries are preserved.
Fresh validation on Oracle GraalVM Java 17.0.12 through mvnw.cmd:
New CI runs on the synchronized head. Full-repository install, fresh fixture regeneration and Docker E2E are not claimed for this follow-up.