2026-09-26 05:37:23 +08:00
|
|
|
package lua
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-10-04 05:22:47 +08:00
|
|
|
"errors"
|
2026-09-26 05:37:23 +08:00
|
|
|
"sync"
|
2026-10-04 05:22:47 +08:00
|
|
|
"time"
|
2026-09-26 05:37:23 +08:00
|
|
|
|
|
|
|
|
glua "github.com/yuin/gopher-lua"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const maxIdleStates = 16
|
|
|
|
|
|
|
|
|
|
// Pool lends each state to one caller at a time. It grows on contention and
|
2026-10-03 21:57:54 +08:00
|
|
|
// keeps up to maxIdleStates idle states until Close. Acquire/Release callers
|
|
|
|
|
// decide reusability; WithState uses its callback's error.
|
2026-09-26 05:37:23 +08:00
|
|
|
type Pool struct {
|
2026-10-04 05:22:47 +08:00
|
|
|
ctx context.Context
|
|
|
|
|
cancel context.CancelFunc
|
|
|
|
|
timeout time.Duration
|
2026-09-26 05:37:23 +08:00
|
|
|
|
|
|
|
|
factory LStateFactory
|
|
|
|
|
idle []*glua.LState
|
2026-10-04 01:36:48 +08:00
|
|
|
top int
|
2026-09-26 05:37:23 +08:00
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
active sync.WaitGroup
|
|
|
|
|
closed bool
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 21:57:54 +08:00
|
|
|
// NewPool tests the factory by creating one state during initialization.
|
2026-10-04 05:22:47 +08:00
|
|
|
func NewPool(ctx context.Context, timeout time.Duration, factory LStateFactory) (*Pool, error) {
|
|
|
|
|
if timeout <= 0 {
|
|
|
|
|
return nil, errors.New("Lua pool timeout must be positive")
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-26 05:37:23 +08:00
|
|
|
poolCtx, cancel := context.WithCancel(ctx)
|
|
|
|
|
|
|
|
|
|
state, err := factory(poolCtx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
cancel()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-04 05:22:47 +08:00
|
|
|
return &Pool{ctx: poolCtx, cancel: cancel, timeout: timeout, factory: factory, idle: []*glua.LState{state}, top: state.GetTop()}, nil
|
2026-09-26 05:37:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Acquire returns an initialized exclusive state, growing the pool if necessary.
|
2026-10-04 05:22:47 +08:00
|
|
|
// ctx is passed to the factory for state creation; nil uses the pool context.
|
|
|
|
|
func (p *Pool) Acquire(ctx context.Context) (*glua.LState, error) {
|
2026-09-26 05:37:23 +08:00
|
|
|
p.mu.Lock()
|
2026-10-04 05:22:47 +08:00
|
|
|
if p.closed {
|
2026-09-26 05:37:23 +08:00
|
|
|
p.mu.Unlock()
|
2026-10-04 05:22:47 +08:00
|
|
|
return nil, errors.New("Lua pool is closed")
|
|
|
|
|
}
|
|
|
|
|
if err := p.ctx.Err(); err != nil {
|
|
|
|
|
p.mu.Unlock()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if ctx == nil {
|
|
|
|
|
ctx = p.ctx
|
|
|
|
|
} else if err := ctx.Err(); err != nil {
|
|
|
|
|
p.mu.Unlock()
|
|
|
|
|
return nil, err
|
2026-09-26 05:37:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
2026-10-04 05:22:47 +08:00
|
|
|
state, err := p.factory(ctx)
|
2026-09-26 05:37:23 +08:00
|
|
|
if err != nil {
|
|
|
|
|
p.active.Done()
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return state, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-04 05:22:47 +08:00
|
|
|
// WithState runs work on an exclusive state and releases it afterward.
|
|
|
|
|
// Nil ctx and zero timeout use pool defaults. The timeout starts after acquisition.
|
|
|
|
|
func (p *Pool) WithState(ctx context.Context, timeout time.Duration, work func(*glua.LState) error) error {
|
|
|
|
|
state, err := p.Acquire(ctx)
|
2026-10-03 21:57:54 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-10-04 05:22:47 +08:00
|
|
|
if ctx == nil {
|
|
|
|
|
ctx = p.ctx
|
|
|
|
|
}
|
|
|
|
|
if timeout == 0 {
|
|
|
|
|
timeout = p.timeout
|
|
|
|
|
}
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
|
|
|
state.SetContext(ctx)
|
2026-10-03 21:57:54 +08:00
|
|
|
reusable := false
|
|
|
|
|
defer func() {
|
2026-10-04 05:22:47 +08:00
|
|
|
cancel()
|
2026-10-03 21:57:54 +08:00
|
|
|
p.Release(state, reusable)
|
|
|
|
|
}()
|
|
|
|
|
err = work(state)
|
|
|
|
|
reusable = err == nil
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-04 05:22:47 +08:00
|
|
|
// Release resets a state for reuse or closes it.
|
2026-09-26 05:37:23 +08:00
|
|
|
func (p *Pool) Release(state *glua.LState, reusable bool) {
|
|
|
|
|
if reusable {
|
2026-10-04 05:22:47 +08:00
|
|
|
state.RemoveContext()
|
2026-10-04 01:36:48 +08:00
|
|
|
state.SetTop(p.top)
|
2026-09-26 05:37:23 +08:00
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-04 05:22:47 +08:00
|
|
|
// Close cancels the pool context, closes idle states, and waits for borrowed states.
|
2026-09-26 05:37:23 +08:00
|
|
|
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()
|
|
|
|
|
}
|