This commit is contained in:
Sky Johnson 2025-03-26 12:18:15 -05:00
parent bc6ef0d882
commit 9b1942903b
5 changed files with 97 additions and 14 deletions

1
.gitignore vendored
View File

@ -27,3 +27,4 @@ config.lua
routes/
static/
libs/
override/

View File

@ -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)

View File

@ -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
func NewContext() *Context {
// Context pool to reduce allocations
var contextPool = sync.Pool{
New: func() interface{} {
return &Context{
Values: make(map[string]any),
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 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

View File

@ -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
func NewHTTPResponse() *HTTPResponse {
// Response pool to reduce allocations
var responsePool = sync.Pool{
New: func() interface{} {
return &HTTPResponse{
Status: 200,
Headers: make(map[string]string),
Cookies: []*http.Cookie{},
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 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

View File

@ -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