mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-30 21:16:03 +00:00
139 lines
3.0 KiB
Go
139 lines
3.0 KiB
Go
package lua
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
|
|
glua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
const maxIdleStates = 16
|
|
|
|
// LStateFactory must initialize a state fully and observe ctx while doing so.
|
|
// The pool owns any non-nil state it returns, even when it also returns an error.
|
|
type LStateFactory func(ctx context.Context) (*glua.LState, error)
|
|
|
|
// Pool lends each state to one caller at a time. It grows on contention and
|
|
// keeps up to maxIdleStates idle states until Close. Callers decide whether a
|
|
// state is reusable.
|
|
type Pool struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
|
|
factory LStateFactory
|
|
idle []*glua.LState
|
|
|
|
mu sync.Mutex
|
|
active sync.WaitGroup
|
|
closed bool
|
|
}
|
|
|
|
// NewPool initializes one state before returning, so top-level errors surface at startup.
|
|
func NewPool(ctx context.Context, factory LStateFactory) (*Pool, error) {
|
|
poolCtx, cancel := context.WithCancel(ctx)
|
|
|
|
// Create one state now to catch factory errors at startup.
|
|
state, err := factory(poolCtx)
|
|
if err != nil {
|
|
cancel()
|
|
if state != nil {
|
|
state.Close()
|
|
}
|
|
return nil, err
|
|
}
|
|
if state == nil {
|
|
cancel()
|
|
return nil, errors.New("Lua state factory returned nil")
|
|
}
|
|
if err := poolCtx.Err(); err != nil {
|
|
state.Close()
|
|
cancel()
|
|
return nil, err
|
|
}
|
|
|
|
return &Pool{ctx: poolCtx, cancel: cancel, factory: factory, idle: []*glua.LState{state}}, nil
|
|
}
|
|
|
|
// Context is cancelled by Close. Query contexts should derive from it.
|
|
func (p *Pool) Context() context.Context {
|
|
return p.ctx
|
|
}
|
|
|
|
// Acquire returns an initialized exclusive state, growing the pool if necessary.
|
|
func (p *Pool) Acquire() (*glua.LState, error) {
|
|
p.mu.Lock()
|
|
if p.closed || p.ctx.Err() != nil {
|
|
p.mu.Unlock()
|
|
return nil, p.ctx.Err()
|
|
}
|
|
|
|
p.active.Add(1)
|
|
|
|
n := len(p.idle)
|
|
if n != 0 {
|
|
state := p.idle[n-1]
|
|
p.idle = p.idle[:n-1]
|
|
p.mu.Unlock()
|
|
return state, nil
|
|
}
|
|
p.mu.Unlock()
|
|
|
|
// TODO: Limit the total number of states. When the limit is reached, wait
|
|
// for a Release instead of creating another state; allow the wait to be
|
|
// cancelled by the caller or by Close.
|
|
state, err := p.factory(p.ctx)
|
|
if err == nil && state == nil {
|
|
err = errors.New("Lua state factory returned nil")
|
|
}
|
|
if err != nil {
|
|
if state != nil {
|
|
state.Close()
|
|
}
|
|
p.active.Done()
|
|
return nil, err
|
|
}
|
|
if err := p.ctx.Err(); err != nil {
|
|
state.Close()
|
|
p.active.Done()
|
|
return nil, err
|
|
}
|
|
|
|
return state, nil
|
|
}
|
|
|
|
// Release returns a healthy state to the pool and closes a failed or cancelled one.
|
|
func (p *Pool) Release(state *glua.LState, reusable bool) {
|
|
if reusable {
|
|
p.mu.Lock()
|
|
if !p.closed && p.ctx.Err() == nil && len(p.idle) < maxIdleStates {
|
|
p.idle = append(p.idle, state)
|
|
} else {
|
|
reusable = false
|
|
}
|
|
p.mu.Unlock()
|
|
}
|
|
|
|
if !reusable {
|
|
state.Close()
|
|
}
|
|
|
|
p.active.Done()
|
|
}
|
|
|
|
// Close cancels active work, closes idle states, and waits for borrowed states.
|
|
func (p *Pool) Close() {
|
|
p.mu.Lock()
|
|
if !p.closed {
|
|
p.closed = true
|
|
p.cancel()
|
|
for _, state := range p.idle {
|
|
state.Close()
|
|
}
|
|
p.idle = nil
|
|
}
|
|
p.mu.Unlock()
|
|
|
|
p.active.Wait()
|
|
}
|