fix(wasmguest): expose host services during register
This commit is contained in:
parent
e2dcbc1fc3
commit
f14846d758
@ -0,0 +1,36 @@
|
||||
# Register sees the current guest host services
|
||||
|
||||
Go guest plugins can declare capability-backed runtime surfaces from their
|
||||
`Register` callback. Bridge providers are the first such surface: the provider
|
||||
stores its invokable value in the guest-local bridge stub while the stub tells
|
||||
the CMS host that the named service exists.
|
||||
|
||||
`Serve` previously assigned the package-level guest runtime only after
|
||||
`newGuest` returned. Because `newGuest` runs `Register` synchronously,
|
||||
`HostServices()` returned an empty `CoreServices` value during registration.
|
||||
Plugins that correctly nil-checked the bridge silently skipped registration.
|
||||
The CMS could therefore mark a Wasm plugin loaded while consumers failed with
|
||||
`bridge: no service registered` during a later load hook.
|
||||
|
||||
`newGuest` now makes the guest under construction current for the synchronous
|
||||
registration pass and restores the previous runtime before returning. `Serve`
|
||||
then installs the completed guest as before. This keeps construction isolated
|
||||
for native tests while making the documented `HostServices()` escape hatch
|
||||
truthful during `Register` on every pooled Wasm instance.
|
||||
|
||||
Alternatives rejected were moving bridge registration into `Load`, which runs
|
||||
on only one pooled instance, and adding bridge names only to the manifest,
|
||||
which would advertise host availability without installing the guest-local
|
||||
`BridgeInvokable` value needed by `HOOK_BRIDGE_CALL`.
|
||||
|
||||
Consequences:
|
||||
|
||||
- Bridge services registered from `Register` exist on every pooled guest
|
||||
instance and remain callable after CMS startup and hot swap.
|
||||
- A regression test exercises the observable `Serve` plus
|
||||
`HOOK_BRIDGE_CALL` contract instead of relying on initialization internals.
|
||||
- No ABI or protobuf change is required; consumers need a plugin SDK release
|
||||
containing the corrected Go guest runtime.
|
||||
|
||||
Keywords: wasmguest.Serve, newGuest, HostServices, PluginRegistration.Register,
|
||||
plugin bridge, RegisterService, BridgeInvokable, HOOK_BRIDGE_CALL, wiki content
|
||||
@ -100,7 +100,15 @@ func newGuest(reg plugin.PluginRegistration) *guest {
|
||||
// Pool whose calls fail cleanly, matching the capability stubs.
|
||||
g.services.Pool = bnwasm.NewPool(bnwasm.Transport(capTransport))
|
||||
g.rag, _ = g.services.RAGService.(*caps.RAGStub)
|
||||
// Register callbacks may use HostServices for capability-backed surfaces
|
||||
// that are not passed as Register parameters, such as bridge services.
|
||||
// Publish this partially initialized guest only for the synchronous
|
||||
// registration pass, then restore the previous runtime. Serve installs g
|
||||
// permanently after newGuest returns.
|
||||
previous := current
|
||||
current = g
|
||||
g.registerErr = runRegister(reg, g.templates, g.blocks)
|
||||
current = previous
|
||||
return g
|
||||
}
|
||||
|
||||
|
||||
@ -130,6 +130,43 @@ func TestServeInstallsRuntime(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeExposesHostServicesDuringRegister(t *testing.T) {
|
||||
previous := current
|
||||
t.Cleanup(func() { current = previous })
|
||||
|
||||
Serve(plugin.PluginRegistration{
|
||||
Name: "bridge-provider",
|
||||
Version: "0.1.0",
|
||||
Register: func(_ templates.TemplateRegistry, _ blocks.BlockRegistry) error {
|
||||
bridge := HostServices().Bridge
|
||||
if bridge == nil {
|
||||
return fmt.Errorf("HostServices bridge is unavailable during Register")
|
||||
}
|
||||
bridge.RegisterService("bridge-provider", "content", bridgeInvokableFunc(
|
||||
func(_ context.Context, _ string, _ []byte) ([]byte, error) {
|
||||
return []byte("registered"), nil
|
||||
},
|
||||
))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
resp := invokeHook(t, current, abiv1.Hook_HOOK_BRIDGE_CALL, &abiv1.BridgeCallRequest{
|
||||
ServiceName: "content",
|
||||
Method: "apply",
|
||||
})
|
||||
if resp.GetError() != nil {
|
||||
t.Fatalf("bridge call returned error: %v", resp.GetError())
|
||||
}
|
||||
bridgeResp := &abiv1.BridgeCallResponse{}
|
||||
if err := proto.Unmarshal(resp.GetPayload(), bridgeResp); err != nil {
|
||||
t.Fatalf("unmarshal BridgeCallResponse: %v", err)
|
||||
}
|
||||
if got := string(bridgeResp.GetPayload()); got != "registered" {
|
||||
t.Fatalf("bridge payload = %q, want registered", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBridgeCallProvidesAuthenticatedCallerContext(t *testing.T) {
|
||||
g := newGuest(fixtureRegistration())
|
||||
g.services.Bridge.RegisterService("fixture", "content", bridgeInvokableFunc(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user