node build fixed

This commit is contained in:
ra_ma
2025-09-20 14:08:38 +01:00
parent c6ebbe069d
commit 3d298fa434
1516 changed files with 535727 additions and 2 deletions

View File

@@ -0,0 +1,229 @@
package goja_runtime
import (
"context"
"fmt"
"runtime"
"seanime/internal/util/result"
"sync"
"sync/atomic"
"github.com/dop251/goja"
"github.com/rs/zerolog"
)
// Manager manages a shared pool of Goja runtimes for all extensions.
type Manager struct {
pluginPools *result.Map[string, *Pool]
basePool *Pool
logger *zerolog.Logger
}
type Pool struct {
sp sync.Pool
factory func() *goja.Runtime
logger *zerolog.Logger
size int32
metrics metrics
}
// metrics holds counters for pool stats.
type metrics struct {
prewarmed atomic.Int64
created atomic.Int64
reused atomic.Int64
timeouts atomic.Int64
invocations atomic.Int64
}
func NewManager(logger *zerolog.Logger) *Manager {
return &Manager{
logger: logger,
}
}
// GetOrCreatePrivatePool returns the pool for the given extension.
func (m *Manager) GetOrCreatePrivatePool(extID string, initFn func() *goja.Runtime) (*Pool, error) {
if m.pluginPools == nil {
m.pluginPools = result.NewResultMap[string, *Pool]()
}
pool, ok := m.pluginPools.Get(extID)
if !ok {
pool = newPool(5, initFn, m.logger)
m.pluginPools.Set(extID, pool)
}
return pool, nil
}
func (m *Manager) DeletePluginPool(extID string) {
m.logger.Trace().Msgf("plugin: Deleting pool for extension %s", extID)
if m.pluginPools == nil {
return
}
// Get the pool first to interrupt all runtimes
if pool, ok := m.pluginPools.Get(extID); ok {
// Drain the pool and interrupt all runtimes
m.logger.Debug().Msgf("plugin: Interrupting all runtimes in pool for extension %s", extID)
interruptedCount := 0
for {
// Get a runtime without using a context to avoid blocking
runtimeV := pool.sp.Get()
if runtimeV == nil {
break // No more runtimes in the pool or error occurred
}
runtime, ok := runtimeV.(*goja.Runtime)
if !ok {
break
}
// Interrupt the runtime
runtime.ClearInterrupt()
interruptedCount++
}
m.logger.Debug().Msgf("plugin: Interrupted %d runtimes in pool for extension %s", interruptedCount, extID)
}
// Delete the pool
m.pluginPools.Delete(extID)
runtime.GC()
}
// GetOrCreateSharedPool returns the shared pool.
func (m *Manager) GetOrCreateSharedPool(initFn func() *goja.Runtime) (*Pool, error) {
if m.basePool == nil {
m.basePool = newPool(15, initFn, m.logger)
}
return m.basePool, nil
}
func (m *Manager) Run(ctx context.Context, extID string, fn func(*goja.Runtime) error) error {
pool, ok := m.pluginPools.Get(extID)
if !ok {
return fmt.Errorf("plugin pool not found for extension ID: %s", extID)
}
runtime, err := pool.Get(ctx)
pool.metrics.invocations.Add(1)
if err != nil {
return err
}
defer pool.Put(runtime)
return fn(runtime)
}
func (m *Manager) RunShared(ctx context.Context, fn func(*goja.Runtime) error) error {
runtime, err := m.basePool.Get(ctx)
if err != nil {
return err
}
defer m.basePool.Put(runtime)
return fn(runtime)
}
func (m *Manager) GetLogger() *zerolog.Logger {
return m.logger
}
func (m *Manager) PrintPluginPoolMetrics(extID string) {
if m.pluginPools == nil {
return
}
pool, ok := m.pluginPools.Get(extID)
if !ok {
return
}
stats := pool.Stats()
m.logger.Trace().
Int64("prewarmed", stats["prewarmed"]).
Int64("created", stats["created"]).
Int64("reused", stats["reused"]).
Int64("timeouts", stats["timeouts"]).
Int64("invocations", stats["invocations"]).
Msg("goja runtime: VM Pool Metrics")
}
func (m *Manager) PrintBasePoolMetrics() {
if m.basePool == nil {
return
}
stats := m.basePool.Stats()
m.logger.Trace().
Int64("prewarmed", stats["prewarmed"]).
Int64("created", stats["created"]).
Int64("reused", stats["reused"]).
Int64("invocations", stats["invocations"]).
Int64("timeouts", stats["timeouts"]).
Msg("goja runtime: Base VM Pool Metrics")
}
// newPool creates a new Pool using sync.Pool, pre-warming it with size items.
func newPool(size int32, initFn func() *goja.Runtime, logger *zerolog.Logger) *Pool {
p := &Pool{
factory: initFn,
logger: logger,
size: size,
}
// p.sp.New = func() interface{} {
// runtime := initFn()
// p.metrics.created.Add(1)
// return runtime
// }
p.sp.New = func() any {
return nil
}
// Pre-warm the pool
logger.Trace().Int32("size", size).Msg("goja runtime: Pre-warming pool")
for i := int32(0); i < size; i++ {
r := initFn()
p.sp.Put(r)
p.metrics.prewarmed.Add(1)
}
return p
}
// Get retrieves a runtime from the pool or creates a new one. It respects the context for cancellation.
func (p *Pool) Get(ctx context.Context) (*goja.Runtime, error) {
v := p.sp.Get()
if v == nil {
// If sync.Pool.New returned nil or context canceled, try factory manually.
select {
case <-ctx.Done():
p.metrics.timeouts.Add(1)
return nil, ctx.Err()
default:
}
runtime := p.factory()
p.metrics.created.Add(1)
return runtime, nil
}
p.metrics.reused.Add(1)
return v.(*goja.Runtime), nil
}
// Put returns a runtime to the pool after clearing its interrupt.
func (p *Pool) Put(runtime *goja.Runtime) {
if runtime == nil {
return
}
runtime.ClearInterrupt()
p.sp.Put(runtime)
}
// Stats returns pool metrics as a map.
func (p *Pool) Stats() map[string]int64 {
return map[string]int64{
"prewarmed": p.metrics.prewarmed.Load(),
"invocations": p.metrics.invocations.Load(),
"created": p.metrics.created.Load(),
"reused": p.metrics.reused.Load(),
"timeouts": p.metrics.timeouts.Load(),
}
}