pools
This commit is contained in:
parent
bc6ef0d882
commit
9b1942903b
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -27,3 +27,4 @@ config.lua
|
||||||
routes/
|
routes/
|
||||||
static/
|
static/
|
||||||
libs/
|
libs/
|
||||||
|
override/
|
||||||
|
|
|
@ -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)
|
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)
|
// 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
|
// Get the actual error from the router - this requires exposing the actual error
|
||||||
// from the node in the GetBytecode method
|
// from the node in the GetBytecode method
|
||||||
errorMsg := "Route exists but failed to compile. Check server logs for details."
|
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
|
// handleLuaRoute executes a Lua route
|
||||||
func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode []byte, scriptPath string, params *routers.Params) {
|
func (s *Server) handleLuaRoute(w http.ResponseWriter, r *http.Request, bytecode []byte, scriptPath string, params *routers.Params) {
|
||||||
ctx := runner.NewContext()
|
ctx := runner.NewContext()
|
||||||
|
defer ctx.Release()
|
||||||
|
|
||||||
// Log bytecode size
|
// Log bytecode size
|
||||||
s.logger.Debug("Executing Lua route with %d bytes of bytecode", len(bytecode))
|
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
|
// Check for HTTPResponse type
|
||||||
if httpResp, ok := result.(*runner.HTTPResponse); ok {
|
if httpResp, ok := result.(*runner.HTTPResponse); ok {
|
||||||
|
defer runner.ReleaseResponse(httpResp)
|
||||||
|
|
||||||
// Set response headers
|
// Set response headers
|
||||||
for name, value := range httpResp.Headers {
|
for name, value := range httpResp.Headers {
|
||||||
w.Header().Set(name, value)
|
w.Header().Set(name, value)
|
||||||
|
|
|
@ -1,16 +1,34 @@
|
||||||
package runner
|
package runner
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
// Context represents execution context for a Lua script
|
// Context represents execution context for a Lua script
|
||||||
type Context struct {
|
type Context struct {
|
||||||
// Generic map for any context values (route params, HTTP request info, etc.)
|
// Generic map for any context values (route params, HTTP request info, etc.)
|
||||||
Values map[string]any
|
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 {
|
func NewContext() *Context {
|
||||||
return &Context{
|
return contextPool.Get().(*Context)
|
||||||
Values: make(map[string]any),
|
}
|
||||||
|
|
||||||
|
// 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
|
// Set adds a value to the context
|
||||||
|
|
|
@ -2,6 +2,7 @@ package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||||
)
|
)
|
||||||
|
@ -14,13 +15,43 @@ type HTTPResponse struct {
|
||||||
Cookies []*http.Cookie `json:"-"`
|
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 {
|
func NewHTTPResponse() *HTTPResponse {
|
||||||
return &HTTPResponse{
|
return responsePool.Get().(*HTTPResponse)
|
||||||
Status: 200,
|
}
|
||||||
Headers: make(map[string]string),
|
|
||||||
Cookies: []*http.Cookie{},
|
// 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
|
// 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")
|
state.GetGlobal("__http_responses")
|
||||||
if state.IsNil(-1) {
|
if state.IsNil(-1) {
|
||||||
state.Pop(1)
|
state.Pop(1)
|
||||||
return response, false
|
ReleaseResponse(response) // Return unused response to pool
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for response at thread index
|
// Check for response at thread index
|
||||||
|
@ -105,7 +137,8 @@ func GetHTTPResponse(state *luajit.State) (*HTTPResponse, bool) {
|
||||||
state.GetTable(-2)
|
state.GetTable(-2)
|
||||||
if state.IsNil(-1) {
|
if state.IsNil(-1) {
|
||||||
state.Pop(2)
|
state.Pop(2)
|
||||||
return response, false
|
ReleaseResponse(response) // Return unused response to pool
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get status
|
// Get status
|
||||||
|
|
|
@ -22,6 +22,13 @@ type StateInitFunc func(*luajit.State) error
|
||||||
// RunnerOption defines a functional option for configuring the LuaRunner
|
// RunnerOption defines a functional option for configuring the LuaRunner
|
||||||
type RunnerOption func(*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
|
// LuaRunner runs Lua scripts using a single Lua state
|
||||||
type LuaRunner struct {
|
type LuaRunner struct {
|
||||||
state *luajit.State // The Lua state
|
state *luajit.State // The Lua state
|
||||||
|
@ -191,7 +198,18 @@ func (r *LuaRunner) RunWithContext(ctx context.Context, bytecode []byte, execCtx
|
||||||
}
|
}
|
||||||
r.mu.RUnlock()
|
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{
|
j := job{
|
||||||
Bytecode: bytecode,
|
Bytecode: bytecode,
|
||||||
Context: execCtx,
|
Context: execCtx,
|
||||||
|
@ -204,16 +222,26 @@ func (r *LuaRunner) RunWithContext(ctx context.Context, bytecode []byte, execCtx
|
||||||
case r.jobQueue <- j:
|
case r.jobQueue <- j:
|
||||||
// Job submitted
|
// Job submitted
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
// Return the channel to the pool before exiting
|
||||||
|
resultChanPool.Put(resultChan)
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for result with context
|
// Wait for result with context
|
||||||
|
var result JobResult
|
||||||
select {
|
select {
|
||||||
case result := <-resultChan:
|
case result = <-resultChan:
|
||||||
return result.Value, result.Error
|
// Got result
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
// Return the channel to the pool before exiting
|
||||||
|
resultChanPool.Put(resultChan)
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the channel to the pool
|
||||||
|
resultChanPool.Put(resultChan)
|
||||||
|
|
||||||
|
return result.Value, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run executes a Lua script
|
// Run executes a Lua script
|
||||||
|
|
Loading…
Reference in New Issue
Block a user