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 } ``` diff --git a/options.go b/options.go index 004b48e..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 diff --git a/runtime.go b/runtime.go index 85615f0..f099bc3 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 with dir %s: %w", cacheDir, 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) diff --git a/runtime_test.go b/runtime_test.go index f0c4160..636d624 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -3,6 +3,7 @@ package qjs_test import ( "context" "fmt" + "os" "sync" "testing" "time" @@ -255,6 +256,39 @@ 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) { + cacheDir := t.TempDir() + rt1, err := qjs.New(qjs.Option{CacheDir: cacheDir, DisableBuildCache: true}) + require.NoError(t, err) + + 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: "", DisableBuildCache: true}) + 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", DisableBuildCache: true}) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to create compilation cache") + }) } // Concurrent Runtime Usage Tests