From 03a03af96c0e9adb63a46e3beb2cd8cac4defb3f Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 19 Mar 2025 20:24:47 -0500 Subject: [PATCH] mem leak fix --- core/runner/luarunner.go | 24 +++++++++++++----------- core/runner/require.go | 15 +++++++++++---- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/core/runner/luarunner.go b/core/runner/luarunner.go index 7154fe0..d28308d 100644 --- a/core/runner/luarunner.go +++ b/core/runner/luarunner.go @@ -3,7 +3,6 @@ package runner import ( "context" "errors" - "path/filepath" "sync" "sync/atomic" @@ -29,7 +28,7 @@ type LuaRunner struct { initFunc StateInitFunc // Optional function to initialize Lua state bufferSize int // Size of the job queue buffer requireCache *RequireCache // Cache for required modules - requireCfg RequireConfig // Configuration for require paths + requireCfg *RequireConfig // Configuration for require paths scriptDir string // Base directory for scripts libDirs []string // Additional library directories } @@ -40,7 +39,7 @@ func NewRunner(options ...RunnerOption) (*LuaRunner, error) { runner := &LuaRunner{ bufferSize: 10, // Default buffer size requireCache: NewRequireCache(), - requireCfg: RequireConfig{ + requireCfg: &RequireConfig{ LibDirs: []string{}, }, } @@ -61,7 +60,13 @@ func NewRunner(options ...RunnerOption) (*LuaRunner, error) { runner.jobQueue = make(chan job, runner.bufferSize) runner.isRunning.Store(true) - // Set up require functionality + // Create a shared config pointer that will be updated per request + runner.requireCfg = &RequireConfig{ + ScriptDir: runner.scriptDir, + LibDirs: runner.libDirs, + } + + // Set up require functionality ONCE if err := SetupRequire(state, runner.requireCache, runner.requireCfg); err != nil { state.Close() return nil, ErrInitFailed @@ -226,14 +231,11 @@ func (r *LuaRunner) eventLoop() { // executeJob runs a script in the sandbox environment func (r *LuaRunner) executeJob(j job) JobResult { - // If the job has a script path, update the require context + // If the job has a script path, update paths without re-registering if j.ScriptPath != "" { - // Update the script directory for require - scriptDir := filepath.Dir(j.ScriptPath) - r.requireCfg.ScriptDir = scriptDir - - // Update in the require cache config - SetupRequire(r.state, r.requireCache, r.requireCfg) + r.mu.Lock() + UpdateRequirePaths(r.requireCfg, j.ScriptPath) + r.mu.Unlock() } // Re-run init function if needed diff --git a/core/runner/require.go b/core/runner/require.go index 27ae50e..9a9da55 100644 --- a/core/runner/require.go +++ b/core/runner/require.go @@ -35,18 +35,18 @@ func NewRequireCache() *RequireCache { } // SetupRequire configures the Lua state with a secure require function -func SetupRequire(state *luajit.State, cache *RequireCache, config RequireConfig) error { +func SetupRequire(state *luajit.State, cache *RequireCache, config *RequireConfig) error { // Register the loader function err := state.RegisterGoFunction("__go_load_module", func(s *luajit.State) int { // Get module name modName := s.ToString(1) if modName == "" { s.PushString("module name required") - return -1 // Return error + return -1 } - // Try to load the module - bytecode, err := findAndCompileModule(s, cache, config, modName) + // Use the pointer to the shared config + bytecode, err := findAndCompileModule(s, cache, *config, modName) if err != nil { if err == ErrModuleNotFound { s.PushString("module '" + modName + "' not found") @@ -114,6 +114,13 @@ func SetupRequire(state *luajit.State, cache *RequireCache, config RequireConfig return state.DoString(setupScript) } +// UpdateRequirePaths updates the require paths in the config without further allocations or re-registering the loader. +func UpdateRequirePaths(config *RequireConfig, scriptPath string) { + if scriptPath != "" { + config.ScriptDir = filepath.Dir(scriptPath) + } +} + // findAndCompileModule finds a module in allowed directories and compiles it to bytecode func findAndCompileModule( state *luajit.State,