Summary
The Extism engine has no reachable way to release a compiled plugin's wazero runtime, and the compiler leaks the runtime on two validation-error paths. Applications that build evaluators dynamically (per-tenant or per-script) accumulate wazero runtimes — mmapped JIT code, WASI FS handles, host modules — with no deterministic reclamation.
1. No public Close/Shutdown API
compiler.Executable.Close (engines/extism/compiler/executable.go:70) closes the wazero runtime, but nothing reachable calls it:
evaluator.Evaluator.execUnit is unexported with no getter (engines/extism/evaluator/evaluator.go:22-48).
platform/script.ExecutableUnit has no Close.
platform.Evaluator (platform/evaluator.go:25) has no Close.
Executable.Close is invoked only from its own unit test. Reclamation therefore relies entirely on wazero's GC finalizers.
Fix: add Close(ctx) error to evaluator.Evaluator (delegating to execUnit.GetContent().(*compiler.Executable).Close) and to ExecutableUnit; consider adding an optional io.Closer-style method to platform.Evaluator so callers can release resources.
2. Compiler leaks the plugin on validation-error paths
engines/extism/compiler/compiler.go:95-115: after c.compileFn succeeds (L85, creating a wazero runtime inside extismSDK.NewCompiledPlugin), the error returns at L97 (test-instance creation failure) and L110 (entry point not found) exit without calling plugin.Close. The success path transfers ownership to Executable, but these failures leave the runtime unreferenced until GC.
Scenario: a service that retries compiling user modules with a wrong entry-point name accrues un-closed runtimes.
Fix: on those error paths, plugin.Close(context.WithoutCancel(ctx)) before returning (e.g. via a named-error defer).
Note
ErrExecutableClosed (executable.go:13) is dead code and GetExtismByteCode doesn't check closed — both become relevant once a Close path exists (item 1).
Impact
Medium to high for long-running services that create Extism evaluators dynamically; low for a process that builds a fixed set once at startup.
Fix
Expose Close on the evaluator and executable unit; close the plugin on the compiler's two validation-error paths. Add tests asserting Close releases the runtime and that a failed compile doesn't leak (e.g. via a spy on the plugin's Close).
Summary
The Extism engine has no reachable way to release a compiled plugin's wazero runtime, and the compiler leaks the runtime on two validation-error paths. Applications that build evaluators dynamically (per-tenant or per-script) accumulate wazero runtimes — mmapped JIT code, WASI FS handles, host modules — with no deterministic reclamation.
1. No public Close/Shutdown API
compiler.Executable.Close(engines/extism/compiler/executable.go:70) closes the wazero runtime, but nothing reachable calls it:evaluator.Evaluator.execUnitis unexported with no getter (engines/extism/evaluator/evaluator.go:22-48).platform/script.ExecutableUnithas noClose.platform.Evaluator(platform/evaluator.go:25) has noClose.Executable.Closeis invoked only from its own unit test. Reclamation therefore relies entirely on wazero's GC finalizers.Fix: add
Close(ctx) errortoevaluator.Evaluator(delegating toexecUnit.GetContent().(*compiler.Executable).Close) and toExecutableUnit; consider adding an optionalio.Closer-style method toplatform.Evaluatorso callers can release resources.2. Compiler leaks the plugin on validation-error paths
engines/extism/compiler/compiler.go:95-115: afterc.compileFnsucceeds (L85, creating a wazero runtime insideextismSDK.NewCompiledPlugin), the error returns at L97 (test-instance creation failure) and L110 (entry point not found) exit without callingplugin.Close. The success path transfers ownership toExecutable, but these failures leave the runtime unreferenced until GC.Scenario: a service that retries compiling user modules with a wrong entry-point name accrues un-closed runtimes.
Fix: on those error paths,
plugin.Close(context.WithoutCancel(ctx))before returning (e.g. via a named-error defer).Note
ErrExecutableClosed(executable.go:13) is dead code andGetExtismByteCodedoesn't checkclosed— both become relevant once a Close path exists (item 1).Impact
Medium to high for long-running services that create Extism evaluators dynamically; low for a process that builds a fixed set once at startup.
Fix
Expose
Closeon the evaluator and executable unit; close the plugin on the compiler's two validation-error paths. Add tests assertingClosereleases the runtime and that a failed compile doesn't leak (e.g. via a spy on the plugin'sClose).