Class WasmRuntime
Thread-safety: chicory Instance is NOT thread-safe, so a fresh
Instance is created for each invocation. The parsed WasmModule,
by contrast, is immutable and freely reusable across threads, so it is
cached (see MODULE_CACHE) keyed by a content hash of
the module bytes — parsing/validating the binary is chicory's most expensive
step and is pure given the bytes, so it is done at most once per distinct module.
ABI. Two export shapes are supported, both returning non-zero for a match:
- Legacy body-only —
match(i32 ptr, i32 len) -> i32. The request body is written into linear memory at offset 0 and the function is called with(0, bodyLength). - Richer request envelope —
match_request(i32 ptr, i32 len) -> i32. A JSON envelope{"version","method","path","queryStringParameters","headers","cookies","body"}is written into linear memory at offset 0 and the function is called with(0, jsonLength). This lets a module read the method, path, query parameters, headers and cookies in addition to the body. SeeENVELOPE_VERSIONfor how the envelope stays backward compatible.
match_request it is preferred; otherwise the runtime
falls back to match with the body only, so existing body-only modules keep
working unchanged.
Execution budget. A WASM module is user-supplied code with unrestricted control
flow, so nothing in the module itself bounds how long an invocation runs — a module containing an
unbounded loop would otherwise pin the calling thread forever. Because callMatch(WasmRequest)
runs during request matching, that would wedge matcher threads and starve the event loop.
Every invocation is therefore run on a shared daemon worker under a wall-clock
budget (mockserver.wasmExecutionTimeoutMillis, default 5000ms; 0 disables the budget
and runs inline on the calling thread). On expiry the worker is interrupted — chicory's interpreter
polls Thread.isInterrupted() and unwinds with a ChicoryInterruptedException — and the
call fails closed without waiting for the worker to die, so a module that somehow ignores
interruption still cannot delay the caller.
This class fails closed: any error returns false (callMatch(java.lang.String)) or
leaves the response unshaped (callShape(org.mockserver.wasm.WasmRequest, org.mockserver.wasm.WasmResponse)). The boundary catches Throwable, not merely
Exception, because the guest is untrusted code whose failure modes are not all
Exceptions. Chicory happens to convert a guest StackOverflowError into a
ChicoryException at its call boundary, but that is chicory's implementation detail and covers
only stack exhaustion inside a guest call — an OutOfMemoryError from an allocating
module, or stack exhaustion in the surrounding host code (envelope serialisation, module parsing), is
not wrapped. Letting any of those escape into the matcher would break the fail-closed guarantee
precisely when a hostile module is exercising it. Errors caught here are logged at WARN rather than
swallowed silently, so a genuine JVM-level problem stays diagnosable.
-
Constructor Summary
ConstructorsConstructorDescriptionWasmRuntime(byte[] wasmBytes) Create a runtime with the default memory page and execution budget limits fromConfigurationProperties.wasmMaxMemoryPages()andConfigurationProperties.wasmExecutionTimeoutMillis().WasmRuntime(byte[] wasmBytes, int maxMemoryPages) Create a runtime with an explicit memory page limit and the default execution budget fromConfigurationProperties.wasmExecutionTimeoutMillis().WasmRuntime(byte[] wasmBytes, int maxMemoryPages, long executionTimeoutMillis) Create a runtime with explicit memory page and execution budget limits.WasmRuntime(byte[] wasmBytes, Configuration configuration) Create a runtime whose limits come from the LIVEConfigurationrather than the static property store. -
Method Summary
Modifier and TypeMethodDescriptionbooleanCall the WASM module with just the request body (legacy body-only ABI).booleancallMatch(WasmRequest request) Call the WASM module with the full request envelope (method, path, headers, body).callShape(WasmRequest request, WasmResponse response) Call the module's optionalSHAPE_RESPONSEexport to (possibly) rewrite the response the matched expectation would return.static voidinvalidate(byte[] wasmBytes) Drop the cached parsed module for the given bytes, if present.static voidClear all cached parsed modules.
-
Constructor Details
-
WasmRuntime
public WasmRuntime(byte[] wasmBytes) Create a runtime with the default memory page and execution budget limits fromConfigurationProperties.wasmMaxMemoryPages()andConfigurationProperties.wasmExecutionTimeoutMillis(). -
WasmRuntime
Create a runtime whose limits come from the LIVEConfigurationrather than the static property store.This is the constructor production call sites must use. Reading the static store instead means a limit set through a
Configurationinstance orPUT /mockserver/configurationis accepted — the endpoint returns 200 and echoes the new value back — while the enforcement point keeps using the old one. Anullconfiguration falls back to the static store, which is the correct source when no instance is in scope.- Parameters:
wasmBytes- the compiled WASM binaryconfiguration- the live configuration supplying the memory-page and execution-budget limits
-
WasmRuntime
public WasmRuntime(byte[] wasmBytes, int maxMemoryPages) Create a runtime with an explicit memory page limit and the default execution budget fromConfigurationProperties.wasmExecutionTimeoutMillis().- Parameters:
wasmBytes- the compiled WASM binarymaxMemoryPages- maximum number of WASM linear memory pages (each page is 64 KiB)
-
WasmRuntime
public WasmRuntime(byte[] wasmBytes, int maxMemoryPages, long executionTimeoutMillis) Create a runtime with explicit memory page and execution budget limits.- Parameters:
wasmBytes- the compiled WASM binarymaxMemoryPages- maximum number of WASM linear memory pages (each page is 64 KiB)executionTimeoutMillis- maximum wall-clock milliseconds a single invocation may run before it is interrupted and fails closed;0or negative disables the budget and runs the invocation inline on the calling thread
-
-
Method Details
-
callMatch
Call the WASM module with just the request body (legacy body-only ABI).Retained for back-compat; equivalent to
callMatch(WasmRequest.ofBody(requestBody)).- Parameters:
requestBody- the HTTP request body (may be null)- Returns:
trueif the module reports a match
-
callMatch
Call the WASM module with the full request envelope (method, path, headers, body).If the module exports
MATCH_REQUESTthe JSON envelope is passed and that function is invoked; otherwise the runtime falls back to the legacyMATCHexport with only the body, preserving back-compat for body-only modules.- Parameters:
request- the request parts to expose to the module (must not be null)- Returns:
trueif the module reports a match
-
callShape
Call the module's optionalSHAPE_RESPONSEexport to (possibly) rewrite the response the matched expectation would return. This is the ABI v3 response-shaping hook.The runtime writes the
shape envelopeinto linear memory at offset 0 and callsshape_response(0, len) -> i64. The module writes its response JSON somewhere in its own linear memory and returns a packed(ptr << 32) | len; the runtime readslenbytes atptrand parses them into aWasmResponse. A return of0means "no change".- Parameters:
request- the matched request parts (serialised under the envelope'srequestfield)response- the response the expectation would return (serialised underresponse)- Returns:
- the parsed shaped response, or
nullwhen the module does not exportSHAPE_RESPONSEor explicitly opts out (returns0) - Throws:
WasmShapeException- if the module traps, returns an out-of-bounds or oversized region, or returns bytes that are not a valid response JSON object — the caller falls back to the unshaped response and logs once (seeWasmResponseShaper)
-
invalidate
public static void invalidate(byte[] wasmBytes) Drop the cached parsed module for the given bytes, if present. Called when a module is unloaded so its parsed form is released promptly. A no-op when the bytes were never cached. Correctness never depends on this (the cache is content-keyed); it only bounds memory. -
invalidateAll
public static void invalidateAll()Clear all cached parsed modules. Called on a full WASM store reset.
-