Fixes race on engine creation with Cache API (#1021)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
5
.github/workflows/commit.yaml
vendored
5
.github/workflows/commit.yaml
vendored
@@ -76,6 +76,11 @@ jobs:
|
|||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
- run: make test
|
- run: make test
|
||||||
|
if: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
# Run tests with -race only on main branch push.
|
||||||
|
- run: make test go_test_options='-race'
|
||||||
|
if: ${{ github.event_name == 'push' }}
|
||||||
|
|
||||||
- name: "Generate coverage report" # only once (not per OS)
|
- name: "Generate coverage report" # only once (not per OS)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
|
|||||||
13
Makefile
13
Makefile
@@ -22,6 +22,9 @@ main_sources := $(wildcard $(filter-out %_test.go $(all_testdata) $(all_testing
|
|||||||
# Paths need to all start with ./, so we do that manually vs foreach which strips it.
|
# Paths need to all start with ./, so we do that manually vs foreach which strips it.
|
||||||
main_packages := $(sort $(foreach f,$(dir $(main_sources)),$(if $(findstring ./,$(f)),./,./$(f))))
|
main_packages := $(sort $(foreach f,$(dir $(main_sources)),$(if $(findstring ./,$(f)),./,./$(f))))
|
||||||
|
|
||||||
|
# By default, we don't run with -race as it's costly to run on every PR.
|
||||||
|
go_test_options ?=
|
||||||
|
|
||||||
ensureCompilerFastest := -ldflags '-X github.com/tetratelabs/wazero/internal/integration_test/vs.ensureCompilerFastest=true'
|
ensureCompilerFastest := -ldflags '-X github.com/tetratelabs/wazero/internal/integration_test/vs.ensureCompilerFastest=true'
|
||||||
.PHONY: bench
|
.PHONY: bench
|
||||||
bench:
|
bench:
|
||||||
@@ -45,7 +48,7 @@ build.bench:
|
|||||||
|
|
||||||
.PHONY: test.examples
|
.PHONY: test.examples
|
||||||
test.examples:
|
test.examples:
|
||||||
@go test ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./imports/go/example/... ./imports/wasi_snapshot_preview1/example/...
|
@go test $(go_test_options) ./examples/... ./imports/assemblyscript/example/... ./imports/emscripten/... ./imports/go/example/... ./imports/wasi_snapshot_preview1/example/...
|
||||||
|
|
||||||
.PHONY: build.examples.as
|
.PHONY: build.examples.as
|
||||||
build.examples.as:
|
build.examples.as:
|
||||||
@@ -174,8 +177,8 @@ build.spectest.v2: # Note: SIMD cases are placed in the "simd" subdirectory.
|
|||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
@go test $$(go list ./... | grep -vE '$(spectest_v1_dir)|$(spectest_v2_dir)') -timeout 120s
|
@go test $(go_test_options) $$(go list ./... | grep -vE '$(spectest_v1_dir)|$(spectest_v2_dir)') -timeout 120s
|
||||||
@cd internal/version/testdata && go test ./... -timeout 120s
|
@cd internal/version/testdata && go test $(go_test_options) ./... -timeout 120s
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
coverpkg = $(subst $(space),$(comma),$(main_packages))
|
coverpkg = $(subst $(space),$(comma),$(main_packages))
|
||||||
@@ -189,10 +192,10 @@ spectest:
|
|||||||
@$(MAKE) spectest.v2
|
@$(MAKE) spectest.v2
|
||||||
|
|
||||||
spectest.v1:
|
spectest.v1:
|
||||||
@go test $$(go list ./... | grep $(spectest_v1_dir)) -timeout 120s
|
@go test $(go_test_options) $$(go list ./... | grep $(spectest_v1_dir)) -timeout 120s
|
||||||
|
|
||||||
spectest.v2:
|
spectest.v2:
|
||||||
@go test $$(go list ./... | grep $(spectest_v2_dir)) -timeout 120s
|
@go test $(go_test_options) $$(go list ./... | grep $(spectest_v2_dir)) -timeout 120s
|
||||||
|
|
||||||
golangci_lint_path := $(shell go env GOPATH)/bin/golangci-lint
|
golangci_lint_path := $(shell go env GOPATH)/bin/golangci-lint
|
||||||
|
|
||||||
|
|||||||
10
cache.go
10
cache.go
@@ -8,6 +8,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/tetratelabs/wazero/api"
|
"github.com/tetratelabs/wazero/api"
|
||||||
"github.com/tetratelabs/wazero/internal/filecache"
|
"github.com/tetratelabs/wazero/internal/filecache"
|
||||||
@@ -50,9 +51,14 @@ func NewCompilationCacheWithDir(dirname string) (CompilationCache, error) {
|
|||||||
type cache struct {
|
type cache struct {
|
||||||
// eng is the engine for this cache. If the cache is configured, the engine is shared across multiple instances of
|
// eng is the engine for this cache. If the cache is configured, the engine is shared across multiple instances of
|
||||||
// Runtime, and its lifetime is not bound to them. Instead, the engine is alive until Cache.Close is called.
|
// Runtime, and its lifetime is not bound to them. Instead, the engine is alive until Cache.Close is called.
|
||||||
eng wasm.Engine
|
eng wasm.Engine
|
||||||
|
|
||||||
fileCache filecache.Cache
|
fileCache filecache.Cache
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cache) initEngine(ne newEngine, ctx context.Context, features api.CoreFeatures) wasm.Engine {
|
||||||
|
c.once.Do(func() { c.eng = ne(ctx, features, c.fileCache) })
|
||||||
|
return c.eng
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements the same method on the Cache interface.
|
// Close implements the same method on the Cache interface.
|
||||||
|
|||||||
@@ -125,13 +125,15 @@ func NewRuntimeConfig() RuntimeConfig {
|
|||||||
return newRuntimeConfig()
|
return newRuntimeConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine
|
||||||
|
|
||||||
type runtimeConfig struct {
|
type runtimeConfig struct {
|
||||||
enabledFeatures api.CoreFeatures
|
enabledFeatures api.CoreFeatures
|
||||||
memoryLimitPages uint32
|
memoryLimitPages uint32
|
||||||
memoryCapacityFromMax bool
|
memoryCapacityFromMax bool
|
||||||
isInterpreter bool
|
isInterpreter bool
|
||||||
dwarfDisabled bool // negative as defaults to enabled
|
dwarfDisabled bool // negative as defaults to enabled
|
||||||
newEngine func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine
|
newEngine newEngine
|
||||||
cache CompilationCache
|
cache CompilationCache
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,10 +116,7 @@ func NewRuntimeWithConfig(ctx context.Context, rConfig RuntimeConfig) Runtime {
|
|||||||
if c := config.cache; c != nil {
|
if c := config.cache; c != nil {
|
||||||
// If the Cache is configured, we share the engine.
|
// If the Cache is configured, we share the engine.
|
||||||
cacheImpl = c.(*cache)
|
cacheImpl = c.(*cache)
|
||||||
if cacheImpl.eng == nil {
|
engine = cacheImpl.initEngine(config.newEngine, ctx, config.enabledFeatures)
|
||||||
cacheImpl.eng = config.newEngine(ctx, config.enabledFeatures, cacheImpl.fileCache)
|
|
||||||
}
|
|
||||||
engine = cacheImpl.eng
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we create a new engine.
|
// Otherwise, we create a new engine.
|
||||||
engine = config.newEngine(ctx, config.enabledFeatures, nil)
|
engine = config.newEngine(ctx, config.enabledFeatures, nil)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -695,3 +696,21 @@ func (e *mockEngine) Close() (err error) {
|
|||||||
e.closed = true
|
e.closed = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestNewRuntime_concurrent ensures that concurrent execution of NewRuntime is race-free.
|
||||||
|
// This depends on -race flag.
|
||||||
|
func TestNewRuntime_concurrent(t *testing.T) {
|
||||||
|
const num = 100
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
config := NewRuntimeConfig().WithCompilationCache(NewCompilationCache())
|
||||||
|
wg.Add(num)
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
r := NewRuntimeWithConfig(testCtx, config)
|
||||||
|
err := r.Close(testCtx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user