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,234 @@
package goja_util
import (
"context"
"fmt"
"runtime/debug"
"sync"
"time"
"github.com/samber/mo"
)
// Job represents a task to be executed in the VM
type Job struct {
fn func() error
resultCh chan error
async bool // Flag to indicate if the job is async (doesn't need to wait for result)
}
// Scheduler handles all VM operations added concurrently in a single goroutine
// Any goroutine that needs to execute a VM operation must schedule it because the UI VM isn't thread safe
type Scheduler struct {
jobQueue chan *Job
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
// Track the currently executing job to detect nested scheduling
currentJob *Job
currentJobLock sync.Mutex
onException mo.Option[func(err error)]
}
func NewScheduler() *Scheduler {
ctx, cancel := context.WithCancel(context.Background())
s := &Scheduler{
jobQueue: make(chan *Job, 9999),
ctx: ctx,
onException: mo.None[func(err error)](),
cancel: cancel,
}
s.start()
return s
}
func (s *Scheduler) SetOnException(onException func(err error)) {
s.onException = mo.Some(onException)
}
func (s *Scheduler) start() {
s.wg.Add(1)
go func() {
defer s.wg.Done()
for {
select {
case <-s.ctx.Done():
return
case job := <-s.jobQueue:
// Set the current job before execution
s.currentJobLock.Lock()
s.currentJob = job
s.currentJobLock.Unlock()
err := job.fn()
// Clear the current job after execution
s.currentJobLock.Lock()
s.currentJob = nil
s.currentJobLock.Unlock()
// Only send result if the job is not async
if !job.async {
job.resultCh <- err
}
if err != nil {
if onException, ok := s.onException.Get(); ok {
onException(err)
}
}
}
}
}()
}
func (s *Scheduler) Stop() {
if s.cancel != nil {
s.cancel()
}
//s.wg.Wait()
}
// Schedule adds a job to the queue and waits for its completion
func (s *Scheduler) Schedule(fn func() error) error {
resultCh := make(chan error, 1)
job := &Job{
fn: func() error {
defer func() {
if r := recover(); r != nil {
resultCh <- fmt.Errorf("panic: %v", r)
}
}()
return fn()
},
resultCh: resultCh,
async: false,
}
// Check if we're already in a job execution context
s.currentJobLock.Lock()
isNestedCall := s.currentJob != nil && !s.currentJob.async
s.currentJobLock.Unlock()
// If this is a nested call from a synchronous job, we need to be careful
// We can't execute directly because the VM isn't thread-safe
// Instead, we'll queue it and use a separate goroutine to wait for the result
if isNestedCall {
// Queue the job
select {
case <-s.ctx.Done():
return fmt.Errorf("scheduler stopped")
case s.jobQueue <- job:
// Create a separate goroutine to wait for the result
// This prevents deadlock while still ensuring the job runs in the scheduler
resultCh2 := make(chan error, 1)
go func() {
resultCh2 <- <-resultCh
}()
return <-resultCh2
}
}
// Otherwise, queue the job normally
select {
case <-s.ctx.Done():
return fmt.Errorf("scheduler stopped")
case s.jobQueue <- job:
return <-resultCh
}
}
// ScheduleAsync adds a job to the queue without waiting for completion
// This is useful for fire-and-forget operations or when a job needs to schedule another job
func (s *Scheduler) ScheduleAsync(fn func() error) {
job := &Job{
fn: func() error {
defer func() {
if r := recover(); r != nil {
// Get stack trace for better identification
stack := debug.Stack()
jobInfo := fmt.Sprintf("async job panic: %v\nStack: %s", r, stack)
if onException, ok := s.onException.Get(); ok {
onException(fmt.Errorf("panic in async job: %v\n%s", r, jobInfo))
}
}
}()
return fn()
},
resultCh: nil, // No result channel needed
async: true,
}
// Queue the job without blocking
select {
case <-s.ctx.Done():
// Scheduler is stopped, just ignore
return
case s.jobQueue <- job:
// Job queued successfully
// fmt.Printf("job queued successfully, length: %d\n", len(s.jobQueue))
return
default:
// Queue is full, log an error
if onException, ok := s.onException.Get(); ok {
onException(fmt.Errorf("async job queue is full"))
}
}
}
// ScheduleWithTimeout schedules a job with a timeout
func (s *Scheduler) ScheduleWithTimeout(fn func() error, timeout time.Duration) error {
resultCh := make(chan error, 1)
job := &Job{
fn: func() error {
defer func() {
if r := recover(); r != nil {
resultCh <- fmt.Errorf("panic: %v", r)
}
}()
return fn()
},
resultCh: resultCh,
async: false,
}
// Check if we're already in a job execution context
s.currentJobLock.Lock()
isNestedCall := s.currentJob != nil && !s.currentJob.async
s.currentJobLock.Unlock()
// If this is a nested call from a synchronous job, handle it specially
if isNestedCall {
// Queue the job
select {
case <-s.ctx.Done():
return fmt.Errorf("scheduler stopped")
case s.jobQueue <- job:
// Create a separate goroutine to wait for the result with timeout
resultCh2 := make(chan error, 1)
go func() {
select {
case err := <-resultCh:
resultCh2 <- err
case <-time.After(timeout):
resultCh2 <- fmt.Errorf("operation timed out")
}
}()
return <-resultCh2
}
}
select {
case <-s.ctx.Done():
return fmt.Errorf("scheduler stopped")
case s.jobQueue <- job:
select {
case err := <-resultCh:
return err
case <-time.After(timeout):
return fmt.Errorf("operation timed out")
}
}
}