Skip to content

wazero: fix guard shutdown leak, logging namespace, and typed trap detection#3790

Merged
lpcox merged 6 commits intomainfrom
copilot/go-fan-improvements-wazero
Apr 14, 2026
Merged

wazero: fix guard shutdown leak, logging namespace, and typed trap detection#3790
lpcox merged 6 commits intomainfrom
copilot/go-fan-improvements-wazero

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

Three actionable improvements to the wazero WASM guard integration identified in a Go module review: WASM runtimes were never closed on shutdown, registry.go was emitting logs under the wrong namespace (guard:context instead of guard:registry), and trap detection relied on fragile string matching.

Changes

Registry.Close() + shutdown wiring

  • Added Close(ctx context.Context) to Registry — iterates guards and calls Close(ctx) on those that implement it
  • Called from both UnifiedServer.Close() and InitiateShutdown() (nil-safe) to release WASM JIT runtime resources on shutdown
  • Previously WithCloseOnContextDone(true) was effectively inert since guards were created with context.Background()

Fix logging namespace in registry.go

  • registry.go was calling log.Printf(...) which resolved to the package-level log var declared in context.go with namespace "guard:context" — meaning DEBUG=guard:registry silently dropped those messages
  • Replaced with logger.LogInfo("guard", ...) for operational events; retained debugLog.Printf for debug-only paths
  • Removed a duplicate debug log line introduced in the fix

Typed sys.ExitError check in isWasmTrap

  • Old implementation: strings.Contains(err.Error(), "wasm error:") — fragile against wazero message format changes, and would incorrectly poison a guard on a clean exit(0) (e.g. TinyGo init)
  • New implementation checks *sys.ExitError via errors.As first; only non-zero exit codes are treated as traps:
func isWasmTrap(err error) bool {
    if err == nil {
        return false
    }
    var exitErr *sys.ExitError
    if errors.As(err, &exitErr) {
        return exitErr.ExitCode() != 0
    }
    return strings.Contains(err.Error(), "wasm error:")
}

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • example.com
    • Triggering command: /tmp/go-build900194802/b514/launcher.test /tmp/go-build900194802/b514/launcher.test -test.testlogfile=/tmp/go-build900194802/b514/testlog.txt -test.paniconexit0 -test.timeout=10m0s -w o4Cr-wAQW cfg x_amd64/vet -c -I /tmp/go-build185-bool x_amd64/vet 1085�� elemetry.io/otel-errorsas cfg x_amd64/vet --gdwarf-5 ions =0 x_amd64/vet (dns block)
  • invalid-host-that-does-not-exist-12345.com
    • Triggering command: /tmp/go-build900194802/b496/config.test /tmp/go-build900194802/b496/config.test -test.testlogfile=/tmp/go-build900194802/b496/testlog.txt -test.paniconexit0 -test.timeout=10m0s /tmp/go-build900194802/b389/vet.cfg g_.a ache/go/1.25.8/x64/src/bufio/bufio.go x_amd64/vet --gdwarf-5 1085977/b140/_cg-atomic -o x_amd64/vet -W _.a /tmp/go-build185-ifaceassert x_amd64/vet . .io/otel/attribu-atomic --64 x_amd64/vet (dns block)
  • nonexistent.local
    • Triggering command: /tmp/go-build900194802/b514/launcher.test /tmp/go-build900194802/b514/launcher.test -test.testlogfile=/tmp/go-build900194802/b514/testlog.txt -test.paniconexit0 -test.timeout=10m0s -w o4Cr-wAQW cfg x_amd64/vet -c -I /tmp/go-build185-bool x_amd64/vet 1085�� elemetry.io/otel-errorsas cfg x_amd64/vet --gdwarf-5 ions =0 x_amd64/vet (dns block)
  • slow.example.com
    • Triggering command: /tmp/go-build900194802/b514/launcher.test /tmp/go-build900194802/b514/launcher.test -test.testlogfile=/tmp/go-build900194802/b514/testlog.txt -test.paniconexit0 -test.timeout=10m0s -w o4Cr-wAQW cfg x_amd64/vet -c -I /tmp/go-build185-bool x_amd64/vet 1085�� elemetry.io/otel-errorsas cfg x_amd64/vet --gdwarf-5 ions =0 x_amd64/vet (dns block)
  • this-host-does-not-exist-12345.com
    • Triggering command: /tmp/go-build900194802/b523/mcp.test /tmp/go-build900194802/b523/mcp.test -test.testlogfile=/tmp/go-build900194802/b523/testlog.txt -test.paniconexit0 -test.timeout=10m0s -o 1085977/b432/_pkg_.a -trimpath x_amd64/vet us.pb.go t/unicode/bidi -lang=go1.25 x_amd64/vet (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI linked an issue Apr 14, 2026 that may be closed by this pull request
…p detection

- Add Registry.Close(ctx) method to close all registered guards that
  implement io.Closer; call it from UnifiedServer.Close() and
  InitiateShutdown() to release WASM runtime resources on shutdown
- Fix logging namespace confusion in registry.go: replace log.Printf
  (which was using the guard:context namespace logger) with
  logger.LogInfo("guard", ...) for operational events
- Use typed sys.ExitError check in isWasmTrap: check exit code 0 as a
  normal process exit (not a trap) before falling back to string matching
- Add tests for Registry.Close and sys.ExitError handling in isWasmTrap

Agent-Logs-Url: https://github.com/github/gh-aw-mcpg/sessions/842dba68-e351-47f7-84e1-cf101f122182

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix resource management in WASM guard shutdown wazero: fix guard shutdown leak, logging namespace, and typed trap detection Apr 14, 2026
Copilot AI requested a review from lpcox April 14, 2026 18:26
@lpcox lpcox marked this pull request as ready for review April 14, 2026 18:34
Copilot AI review requested due to automatic review settings April 14, 2026 18:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the wazero-based WASM guard integration by ensuring guard runtimes are released on shutdown, fixing guard registry logging to use the intended logging mechanism, and making WASM trap detection more robust by using typed sys.ExitError checks instead of only string matching.

Changes:

  • Add Registry.Close(ctx) and wire it into UnifiedServer.Close() and UnifiedServer.InitiateShutdown() to release guard-held WASM runtime resources.
  • Fix internal/guard/registry.go operational logs to use logger.LogInfo("guard", ...) and remove a duplicate log line.
  • Update isWasmTrap to treat sys.ExitError with exit code 0 as non-trap; add focused unit tests for exit-code behavior.
Show a summary per file
File Description
internal/server/unified.go Calls into the guard registry during shutdown/close to release guard runtime resources.
internal/guard/registry.go Adds Registry.Close(ctx) and adjusts logging away from the wrong debug namespace.
internal/guard/wasm.go Improves trap detection by checking typed WASI exit errors before falling back to string matching.
internal/guard/wasm_test.go Adds test coverage for isWasmTrap behavior with sys.ExitError (including wrapped errors).
internal/guard/guard_test.go Adds unit tests validating registry Close behavior across closable/non-closable guards and error cases.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/5 changed files
  • Comments generated: 4

Comment thread internal/guard/registry.go Outdated
Comment thread internal/guard/registry.go Outdated
Comment thread internal/server/unified.go Outdated
Comment on lines +759 to +762
// Release WASM runtime resources held by guards
if us.guardRegistry != nil {
us.guardRegistry.Close(context.Background())
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InitiateShutdown closes guards immediately while the HTTP server may still be draining in-flight requests. If any in-flight request is currently executing a guard call, closing the underlying WASM module/runtime concurrently can race (WasmGuard serializes calls with a mutex, but Close does not take that mutex). Consider either deferring guardRegistry.Close until after HTTP shutdown/drain completes, or ensuring guard Close implementations synchronize with in-flight calls (e.g. WasmGuard.Close acquiring g.mu).

Copilot uses AI. Check for mistakes.
lpcox and others added 3 commits April 14, 2026 11:41
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@lpcox
Copy link
Copy Markdown
Collaborator

lpcox commented Apr 14, 2026

Review Feedback

Three items — I'll push commits for each.

1. Registry.Close() holds a write lock for a read-only scan

r.mu.Lock() is used to iterate r.guards and build the closers list, but the map is only read — not modified. This should be r.mu.RLock() / r.mu.RUnlock() to avoid unnecessarily blocking concurrent readers during shutdown.

2. No operational log on successful guard close

Close() logs on failure (LogWarn) but is silent on success. During shutdown, a summary line like "Closed N guard(s)" improves operational visibility and confirms the cleanup actually ran.

3. Missing test: double-close is safe

The test suite covers empty registry, non-closable guards, multiple guards, and error handling — but doesn't verify that calling Registry.Close() twice is safe. Since WasmGuard.Close() delegates to wazero's module.Close() / runtime.Close() (which are idempotent), it's important to confirm the registry level also handles this gracefully.

- Use RLock/RUnlock instead of Lock/Unlock in Registry.Close() since
  the guards map is only read (not modified) during the closers scan
- Add summary LogInfo after closing guards for shutdown visibility
- Add double-close idempotency test with closeCount tracking on mock

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lpcox lpcox merged commit 698475e into main Apr 14, 2026
14 checks passed
@lpcox lpcox deleted the copilot/go-fan-improvements-wazero branch April 14, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[go-fan] Go Module Review: tetratelabs/wazero

3 participants