Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ require (
github.com/wippyai/tree-sitter-markdown v0.0.3
github.com/wippyai/tree-sitter-sql v0.0.4
github.com/wippyai/wapp v0.1.2
github.com/wippyai/wasm-runtime v0.0.0-20260209224309-586ea6933075
github.com/wippyai/wasm-runtime v0.0.0-20260619200926-cc678f6c6549
github.com/xuri/excelize/v2 v2.10.1
go.opentelemetry.io/otel v1.44.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -595,8 +595,8 @@ github.com/wippyai/tree-sitter-sql v0.0.4 h1:0hJyjkV6/lhoGA5FJAaf96od2xyo+OJINSE
github.com/wippyai/tree-sitter-sql v0.0.4/go.mod h1:QN9CfIO55fwQNVNk1p/7pjxrLk7iKxrQs42Zh6lrr84=
github.com/wippyai/wapp v0.1.2 h1:fCoxKr9s3gk+pWx4XcnIQmOVgPiuimXf3QcE9mHbdmw=
github.com/wippyai/wapp v0.1.2/go.mod h1:ndCkYR80+osLGbd7AFWlP+3DxwooR+R6cxQYPZhksg4=
github.com/wippyai/wasm-runtime v0.0.0-20260209224309-586ea6933075 h1:TZZCgi+CTZKcHRhpziSl9t1T33Dq+VWBNZQ9w6e9b+M=
github.com/wippyai/wasm-runtime v0.0.0-20260209224309-586ea6933075/go.mod h1:1AVYdZibORqlBez081Seakxv2u9IR+KIMrvlBKsk2bQ=
github.com/wippyai/wasm-runtime v0.0.0-20260619200926-cc678f6c6549 h1:XLrkn5LIgjIIyJ2pJ7WnGYyn9vCfP1K252wCw67mgiw=
github.com/wippyai/wasm-runtime v0.0.0-20260619200926-cc678f6c6549/go.mod h1:1AVYdZibORqlBez081Seakxv2u9IR+KIMrvlBKsk2bQ=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
Expand Down
19 changes: 18 additions & 1 deletion runtime/wasm/engine/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ import (
wasmrt "github.com/wippyai/wasm-runtime/runtime"
)

// recycleWarmInstanceAfter bounds how many synchronous calls a reused warm
// instance serves before it is recycled. WASM linear memory only ever grows, so a
// warm instance kept alive across calls accumulates each call's guest allocations;
// after this many calls the instance is closed so the next call instantiates a
// fresh one and reclaims the memory. Re-instantiation is safe because wasm-runtime
// rebuilds the per-instance import bridges, so this keeps the fast warm path for the
// common case while bounding linear-memory growth, at the cost of one re-instantiation
// (and bridge recompile) per recycle.
const recycleWarmInstanceAfter = 512

// Transport can map runtime payloads into call args and map call results back.
type Transport interface {
Prepare(ctx context.Context, input payload.Payloads) ([]any, error)
Expand Down Expand Up @@ -55,6 +65,7 @@ type Process struct {
waitingYield bool
done bool
started bool
warmCalls int
}

// NewProcess creates a scheduler process for WASM execution.
Expand Down Expand Up @@ -141,7 +152,12 @@ func (p *Process) stepSync(out *process.StepOutput) error {

p.result = result
p.done = true
p.softReset()
p.warmCalls++
if p.warmCalls >= recycleWarmInstanceAfter {
p.endExecution()
} else {
p.softReset()
}
out.Done(result)
return nil
}
Expand Down Expand Up @@ -257,6 +273,7 @@ func (p *Process) endExecution() {
_ = p.inst.Close(context.Background())
p.inst = nil
}
p.warmCalls = 0
p.softReset()
}

Expand Down
57 changes: 57 additions & 0 deletions runtime/wasm/engine/process_transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,67 @@ import (

ctxapi "github.com/wippyai/runtime/api/context"
"github.com/wippyai/runtime/api/payload"
"github.com/wippyai/runtime/api/process"
wasmapi "github.com/wippyai/runtime/api/runtime/wasm"
wasmrt "github.com/wippyai/wasm-runtime/runtime"
)

// TestProcessRecyclesWarmInstanceAfterThreshold verifies that a reused warm
// instance is kept across synchronous calls and recycled (closed, so the next
// call instantiates fresh and reclaims linear memory) once it has served
// recycleWarmInstanceAfter calls. This bounds the WASM linear-memory growth that
// otherwise accumulated unbounded across reused calls.
func TestProcessRecyclesWarmInstanceAfterThreshold(t *testing.T) {
ctx := context.Background()
rt, mod := compileEchoModule(ctx, t)
defer func() { _ = rt.Close(ctx) }()

p := &Process{module: mod, method: "run"}
var out process.StepOutput

for i := 1; i <= recycleWarmInstanceAfter+1; i++ {
if p.inst == nil {
inst, err := mod.Instantiate(ctx)
if err != nil {
t.Fatalf("call %d: Instantiate() error = %v", i, err)
}
p.inst = inst
}
// softReset clears execCtx after each sync call; restore it like
// startExecution would for the next call.
p.execCtx = ctx

out.Reset()
if err := p.stepSync(&out); err != nil {
t.Fatalf("call %d: stepSync() error = %v", i, err)
}

switch {
case i < recycleWarmInstanceAfter:
if p.inst == nil {
t.Fatalf("call %d: warm instance recycled before the threshold", i)
}
if p.warmCalls != i {
t.Fatalf("call %d: warmCalls = %d, want %d", i, p.warmCalls, i)
}
case i == recycleWarmInstanceAfter:
if p.inst != nil {
t.Fatalf("call %d: warm instance not recycled at the threshold", i)
}
if p.warmCalls != 0 {
t.Fatalf("call %d: warmCalls = %d after recycle, want 0", i, p.warmCalls)
}
default:
if p.inst == nil {
t.Fatalf("call %d: expected a fresh warm instance after recycle", i)
}
if p.warmCalls != i-recycleWarmInstanceAfter {
t.Fatalf("call %d: warmCalls = %d, want %d", i, p.warmCalls, i-recycleWarmInstanceAfter)
}
}
}
}

type processTestTransportRegistry struct {
items map[string]any
}
Expand Down