From 66dde3a8aca429c11c528e5edcd56eaf4e659eb0 Mon Sep 17 00:00:00 2001 From: Daniel Sloof Date: Fri, 24 Oct 2025 21:34:44 +0200 Subject: [PATCH 1/5] add cacheDir option --- options.go | 1 + runtime.go | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/options.go b/options.go index 004b48e..906bc40 100644 --- a/options.go +++ b/options.go @@ -46,6 +46,7 @@ type Option struct { ProxyFunction any Stdout io.Writer Stderr io.Writer + CacheDir string } // EvalOption configures JavaScript evaluation behavior in QuickJS context. diff --git a/runtime.go b/runtime.go index 85615f0..5ba4580 100644 --- a/runtime.go +++ b/runtime.go @@ -40,6 +40,7 @@ func createGlobalCompiledModule( ctx context.Context, closeOnContextDone bool, disableBuildCache bool, + cacheDir string, quickjsWasmBytes ...[]byte, ) (err error) { // Protect global compilation state with mutex @@ -58,7 +59,13 @@ func createGlobalCompiledModule( // Check if we need to compile or recompile if compiledQJSModule == nil || cachedBytesHash != currentHash || disableBuildCache { - cache := wazero.NewCompilationCache() + var cache wazero.CompilationCache + if cacheDir == "" { + cache = wazero.NewCompilationCache() + } else if cache, err = wazero.NewCompilationCacheWithDir(cacheDir); err != nil { + return fmt.Errorf("failed to create compilation cache: %w", err) + } + cachedRuntimeConfig = wazero. NewRuntimeConfig(). WithCompilationCache(cache). @@ -96,6 +103,7 @@ func New(options ...Option) (runtime *Runtime, err error) { option.Context, option.CloseOnContextDone, option.DisableBuildCache, + option.CacheDir, option.QuickJSWasmBytes, ); err != nil { return nil, fmt.Errorf("failed to create global compiled module: %w", err) From 511350c92ff752ca050f1a6ba6b312b15b64894f Mon Sep 17 00:00:00 2001 From: Daniel Sloof Date: Fri, 24 Oct 2025 22:11:47 +0200 Subject: [PATCH 2/5] move cache option --- options.go | 2 +- runtime.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/options.go b/options.go index 906bc40..df515c3 100644 --- a/options.go +++ b/options.go @@ -38,6 +38,7 @@ type Option struct { // because every operation must check the done context, which introduces additional overhead. CloseOnContextDone bool DisableBuildCache bool + CacheDir string MemoryLimit int MaxStackSize int MaxExecutionTime int @@ -46,7 +47,6 @@ type Option struct { ProxyFunction any Stdout io.Writer Stderr io.Writer - CacheDir string } // EvalOption configures JavaScript evaluation behavior in QuickJS context. diff --git a/runtime.go b/runtime.go index 5ba4580..b7a48d5 100644 --- a/runtime.go +++ b/runtime.go @@ -60,7 +60,7 @@ func createGlobalCompiledModule( // Check if we need to compile or recompile if compiledQJSModule == nil || cachedBytesHash != currentHash || disableBuildCache { var cache wazero.CompilationCache - if cacheDir == "" { + if cacheDir == "" || disableBuildCache { cache = wazero.NewCompilationCache() } else if cache, err = wazero.NewCompilationCacheWithDir(cacheDir); err != nil { return fmt.Errorf("failed to create compilation cache: %w", err) From dd6cf2bfa1bdb86508b8ef5f1cb35e421b770d7d Mon Sep 17 00:00:00 2001 From: Daniel Sloof Date: Fri, 24 Oct 2025 22:14:19 +0200 Subject: [PATCH 3/5] add CacheDir option to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3ac5d19..8e3c784 100644 --- a/README.md +++ b/README.md @@ -719,6 +719,7 @@ type Option struct { MemoryLimit int // Memory usage limit MaxExecutionTime int // Execution timeout GCThreshold int // GC trigger threshold + CacheDir string // Compilation cache directory } ``` From 5b7777a702f155c2dcfaf834fdf9b5c475ac1fb8 Mon Sep 17 00:00:00 2001 From: Daniel Sloof Date: Mon, 27 Oct 2025 10:17:19 +0100 Subject: [PATCH 4/5] add CacheDir tests --- runtime.go | 2 +- runtime_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index b7a48d5..bed2f2a 100644 --- a/runtime.go +++ b/runtime.go @@ -63,7 +63,7 @@ func createGlobalCompiledModule( if cacheDir == "" || disableBuildCache { cache = wazero.NewCompilationCache() } else if cache, err = wazero.NewCompilationCacheWithDir(cacheDir); err != nil { - return fmt.Errorf("failed to create compilation cache: %w", err) + return fmt.Errorf("failed to create compilation cache with dir %s: %w", cacheDir, err) } cachedRuntimeConfig = wazero. diff --git a/runtime_test.go b/runtime_test.go index f0c4160..b6b9f3e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -255,6 +255,34 @@ func TestRuntime(t *testing.T) { require.NoError(t, err, "Normal runtime creation should still work after invalid attempt") defer rt2.Close() }) + + t.Run("CacheDirOption", func(t *testing.T) { + rt1, err := qjs.New(qjs.Option{CacheDir: t.TempDir()}) + require.NoError(t, err) + defer rt1.Close() + + val, err := rt1.Eval("test.js", qjs.Code("21 + 21")) + require.NoError(t, err) + assert.Equal(t, int32(42), val.Int32()) + val.Free() + }) + + t.Run("CacheDirWithEmptyString", func(t *testing.T) { + rt, err := qjs.New(qjs.Option{CacheDir: ""}) + require.NoError(t, err) + defer rt.Close() + + val, err := rt.Eval("test.js", qjs.Code("2 * 21")) + require.NoError(t, err) + assert.Equal(t, int32(42), val.Int32()) + val.Free() + }) + + t.Run("CacheDirWithInvalidPath", func(t *testing.T) { + _, err := qjs.New(qjs.Option{CacheDir: "/invalid/path/that/does/not/exist", QuickJSWasmBytes: []byte("cachedir")}) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to create compilation cache") + }) } // Concurrent Runtime Usage Tests From 8aa28576439a21b79e18c277566de5e265d6ea83 Mon Sep 17 00:00:00 2001 From: Daniel Sloof Date: Mon, 27 Oct 2025 10:36:05 +0100 Subject: [PATCH 5/5] check if cache directory not empty after closing runtime --- runtime.go | 2 +- runtime_test.go | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/runtime.go b/runtime.go index bed2f2a..f099bc3 100644 --- a/runtime.go +++ b/runtime.go @@ -60,7 +60,7 @@ func createGlobalCompiledModule( // Check if we need to compile or recompile if compiledQJSModule == nil || cachedBytesHash != currentHash || disableBuildCache { var cache wazero.CompilationCache - if cacheDir == "" || disableBuildCache { + if cacheDir == "" { cache = wazero.NewCompilationCache() } else if cache, err = wazero.NewCompilationCacheWithDir(cacheDir); err != nil { return fmt.Errorf("failed to create compilation cache with dir %s: %w", cacheDir, err) diff --git a/runtime_test.go b/runtime_test.go index b6b9f3e..636d624 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -3,6 +3,7 @@ package qjs_test import ( "context" "fmt" + "os" "sync" "testing" "time" @@ -257,18 +258,23 @@ func TestRuntime(t *testing.T) { }) t.Run("CacheDirOption", func(t *testing.T) { - rt1, err := qjs.New(qjs.Option{CacheDir: t.TempDir()}) + cacheDir := t.TempDir() + rt1, err := qjs.New(qjs.Option{CacheDir: cacheDir, DisableBuildCache: true}) require.NoError(t, err) - defer rt1.Close() val, err := rt1.Eval("test.js", qjs.Code("21 + 21")) require.NoError(t, err) assert.Equal(t, int32(42), val.Int32()) val.Free() + + rt1.Close() + entries, err := os.ReadDir(cacheDir) + require.NoError(t, err) + assert.NotEmpty(t, entries, "Cache directory should not be empty") }) t.Run("CacheDirWithEmptyString", func(t *testing.T) { - rt, err := qjs.New(qjs.Option{CacheDir: ""}) + rt, err := qjs.New(qjs.Option{CacheDir: "", DisableBuildCache: true}) require.NoError(t, err) defer rt.Close() @@ -279,7 +285,7 @@ func TestRuntime(t *testing.T) { }) t.Run("CacheDirWithInvalidPath", func(t *testing.T) { - _, err := qjs.New(qjs.Option{CacheDir: "/invalid/path/that/does/not/exist", QuickJSWasmBytes: []byte("cachedir")}) + _, err := qjs.New(qjs.Option{CacheDir: "/invalid/path/that/does/not/exist", DisableBuildCache: true}) require.Error(t, err) assert.Contains(t, err.Error(), "failed to create compilation cache") })