Problem
WithHostState documents that its value is created once per Instantiate call and lives exactly as long as that instance, but its API accepts an already-created value and stores that same value every time the Option is applied:
func WithHostState(key, value any) Option {
return func(c *config) {
if c.hostState == nil {
c.hostState = map[any]any{}
}
c.hostState[key] = value
}
}
Relevant code:
|
// whatever the rep names. |
|
func WithHostResourceDtor(tag uint32, fn func(ctx context.Context, rep uint32) error) Option { |
|
return withHostResourceDtor(tag, fn) |
|
} |
|
|
|
// WithHostState attaches an opaque value to every Instance built with this |
|
// option, retrievable via Instance.HostState(key). It is how a host |
|
// implementation of a stateful interface keeps per-instance state: the value |
|
// is created once per Instantiate call and lives exactly as long as the |
|
// Instance. |
|
// |
|
// key should be a package-private type, not a bare string, so two independent |
|
// host implementations cannot collide. |
|
func WithHostState(key, value any) Option { |
|
return func(c *config) { |
|
if c.hostState == nil { |
|
c.hostState = map[any]any{} |
|
} |
Functional options are naturally reusable, and this package explicitly supports repeated Instantiate calls with shared options such as WithCompileCache. Reusing an option containing WithHostState(key, ptr) therefore attaches the same pointer to every instance rather than creating per-instance state.
Impact
Stateful hosts can accidentally share mutable resource maps and request state across isolated component instances. This is not only a documentation mismatch: wago-org/wasi/p2.WithWASI returns reusable-looking []component.Option values that capture one filesystem host, socket host, poll host, and HTTP host. Reusing that slice for two instantiations causes both instances to share those objects; their resource hooks also overwrite the captured handle-table pointer with the most recently instantiated table.
That permits cross-instance state corruption, races, stale-handle resolution against the wrong table, and unintended resource visibility between instances.
Suggested fix
Provide a factory form, for example:
func WithHostStateFactory(key any, newState func() any) Option
and run the factory while building each instantiation's config. Stateful host packages also need a way to build a coherent bundle of imports, resource hooks, destructors, and host state from the same fresh per-instance object—either an option-factory abstraction or an option whose application allocates the bundle and registers all associated callbacks into that config.
The existing WithHostState(key, value) could remain as an explicitly shared-state primitive, but its documentation should no longer claim per-instance creation.
Regression test
Create one option value, reuse it for two Instantiate calls, and assert:
HostState(key) points to distinct objects;
- mutating one instance's host state cannot be observed from the other;
- each
WithResourcesHook-backed host resolves handles only through its own instance's table.
A companion WASI test should reuse opts := wasip2.With(cfg) across two instances and verify that filesystem/HTTP/socket reps and drops remain isolated.
Problem
WithHostStatedocuments that its value is created once perInstantiatecall and lives exactly as long as that instance, but its API accepts an already-created value and stores that same value every time theOptionis applied:Relevant code:
component-model/internal/instance/public_host.go
Lines 61 to 78 in 8a61139
Functional options are naturally reusable, and this package explicitly supports repeated
Instantiatecalls with shared options such asWithCompileCache. Reusing an option containingWithHostState(key, ptr)therefore attaches the same pointer to every instance rather than creating per-instance state.Impact
Stateful hosts can accidentally share mutable resource maps and request state across isolated component instances. This is not only a documentation mismatch:
wago-org/wasi/p2.WithWASIreturns reusable-looking[]component.Optionvalues that capture one filesystem host, socket host, poll host, and HTTP host. Reusing that slice for two instantiations causes both instances to share those objects; their resource hooks also overwrite the captured handle-table pointer with the most recently instantiated table.That permits cross-instance state corruption, races, stale-handle resolution against the wrong table, and unintended resource visibility between instances.
Suggested fix
Provide a factory form, for example:
and run the factory while building each instantiation's config. Stateful host packages also need a way to build a coherent bundle of imports, resource hooks, destructors, and host state from the same fresh per-instance object—either an option-factory abstraction or an option whose application allocates the bundle and registers all associated callbacks into that
config.The existing
WithHostState(key, value)could remain as an explicitly shared-state primitive, but its documentation should no longer claim per-instance creation.Regression test
Create one option value, reuse it for two
Instantiatecalls, and assert:HostState(key)points to distinct objects;WithResourcesHook-backed host resolves handles only through its own instance's table.A companion WASI test should reuse
opts := wasip2.With(cfg)across two instances and verify that filesystem/HTTP/socket reps and drops remain isolated.