diff --git a/.gitignore b/.gitignore index ed0c89d..ae804f7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ config.lua routes/ static/ libs/ +override/ diff --git a/core/http/server.go b/core/http/server.go index 336a28d..ea0eac9 100644 --- a/core/http/server.go +++ b/core/http/server.go @@ -101,7 +101,7 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) { bytecode, scriptPath, found := s.luaRouter.GetBytecode(r.Method, r.URL.Path, params) // Check if we found a route but it has no valid bytecode (compile error) - if found && (bytecode == nil || len(bytecode) == 0) { + if found && len(bytecode) == 0 { // Get the actual error from the router - this requires exposing the actual error // from the node in the GetBytecode method errorMsg := "Route exists but failed to compile. Check server logs for details." @@ -147,6 +147,7 @@ func (s *Server) HandleMethodNotAllowed(w http.ResponseWriter, r *http.Request) // handleLuaRoute executes a Lua route func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode []byte, scriptPath string, params *routers.Params) { ctx := runner.NewContext() + defer ctx.Release() // Log bytecode size s.logger.Debug("Executing Lua route with %d bytes of bytecode", len(bytecode)) @@ -234,6 +235,8 @@ func writeResponse(w http.ResponseWriter, result any, log *logger.Logger) { // Check for HTTPResponse type if httpResp, ok := result.(*runner.HTTPResponse); ok { + defer runner.ReleaseResponse(httpResp) + // Set response headers for name, value := range httpResp.Headers { w.Header().Set(name, value) diff --git a/core/runner/context.go b/core/runner/context.go index 7052a3a..3078093 100644 --- a/core/runner/context.go +++ b/core/runner/context.go @@ -1,16 +1,34 @@ package runner +import "sync" + // Context represents execution context for a Lua script type Context struct { // Generic map for any context values (route params, HTTP request info, etc.) Values map[string]any } -// NewContext creates a new context with initialized maps +// Context pool to reduce allocations +var contextPool = sync.Pool{ + New: func() interface{} { + return &Context{ + Values: make(map[string]any, 16), // Pre-allocate with reasonable capacity + } + }, +} + +// NewContext creates a new context, potentially reusing one from the pool func NewContext() *Context { - return &Context{ - Values: make(map[string]any), + return contextPool.Get().(*Context) +} + +// Release returns the context to the pool after clearing its values +func (c *Context) Release() { + // Clear all values to prevent data leakage + for k := range c.Values { + delete(c.Values, k) } + contextPool.Put(c) } // Set adds a value to the context diff --git a/core/runner/http.go b/core/runner/http.go index 40cd406..00890cc 100644 --- a/core/runner/http.go +++ b/core/runner/http.go @@ -2,6 +2,7 @@ package runner import ( "net/http" + "sync" luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) @@ -14,13 +15,43 @@ type HTTPResponse struct { Cookies []*http.Cookie `json:"-"` } -// NewHTTPResponse creates a default HTTP response +// Response pool to reduce allocations +var responsePool = sync.Pool{ + New: func() interface{} { + return &HTTPResponse{ + Status: 200, + Headers: make(map[string]string, 8), // Pre-allocate with reasonable capacity + Cookies: make([]*http.Cookie, 0, 4), // Pre-allocate with reasonable capacity + } + }, +} + +// NewHTTPResponse creates a default HTTP response, potentially reusing one from the pool func NewHTTPResponse() *HTTPResponse { - return &HTTPResponse{ - Status: 200, - Headers: make(map[string]string), - Cookies: []*http.Cookie{}, + return responsePool.Get().(*HTTPResponse) +} + +// ReleaseResponse returns the response to the pool after clearing its values +func ReleaseResponse(resp *HTTPResponse) { + if resp == nil { + return } + + // Clear all values to prevent data leakage + resp.Status = 200 // Reset to default + + // Clear headers + for k := range resp.Headers { + delete(resp.Headers, k) + } + + // Clear cookies + resp.Cookies = resp.Cookies[:0] // Keep capacity but set length to 0 + + // Clear body + resp.Body = nil + + responsePool.Put(resp) } // LuaHTTPModule is the pure Lua implementation of the HTTP module @@ -97,7 +128,8 @@ func GetHTTPResponse(state *luajit.State) (*HTTPResponse, bool) { state.GetGlobal("__http_responses") if state.IsNil(-1) { state.Pop(1) - return response, false + ReleaseResponse(response) // Return unused response to pool + return nil, false } // Check for response at thread index @@ -105,7 +137,8 @@ func GetHTTPResponse(state *luajit.State) (*HTTPResponse, bool) { state.GetTable(-2) if state.IsNil(-1) { state.Pop(2) - return response, false + ReleaseResponse(response) // Return unused response to pool + return nil, false } // Get status diff --git a/core/runner/luarunner.go b/core/runner/luarunner.go index 667910f..a694bd0 100644 --- a/core/runner/luarunner.go +++ b/core/runner/luarunner.go @@ -22,6 +22,13 @@ type StateInitFunc func(*luajit.State) error // RunnerOption defines a functional option for configuring the LuaRunner type RunnerOption func(*LuaRunner) +// Result channel pool to reduce allocations +var resultChanPool = sync.Pool{ + New: func() interface{} { + return make(chan JobResult, 1) + }, +} + // LuaRunner runs Lua scripts using a single Lua state type LuaRunner struct { state *luajit.State // The Lua state @@ -191,7 +198,18 @@ func (r *LuaRunner) RunWithContext(ctx context.Context, bytecode []byte, execCtx } r.mu.RUnlock() - resultChan := make(chan JobResult, 1) + // Get a result channel from the pool + resultChanInterface := resultChanPool.Get() + resultChan := resultChanInterface.(chan JobResult) + + // Make sure to clear any previous results + select { + case <-resultChan: + // Drain the channel if it has a value + default: + // Channel is already empty + } + j := job{ Bytecode: bytecode, Context: execCtx, @@ -204,16 +222,26 @@ func (r *LuaRunner) RunWithContext(ctx context.Context, bytecode []byte, execCtx case r.jobQueue <- j: // Job submitted case <-ctx.Done(): + // Return the channel to the pool before exiting + resultChanPool.Put(resultChan) return nil, ctx.Err() } // Wait for result with context + var result JobResult select { - case result := <-resultChan: - return result.Value, result.Error + case result = <-resultChan: + // Got result case <-ctx.Done(): + // Return the channel to the pool before exiting + resultChanPool.Put(resultChan) return nil, ctx.Err() } + + // Return the channel to the pool + resultChanPool.Put(resultChan) + + return result.Value, result.Error } // Run executes a Lua script