clearlove2.1
1// 插件运行时:每插件一条串行消息队列。
2//
3// goja 的 Runtime 不是 goroutine-safe,所有脚本访问(setup、路由、事件、
4// 过滤器、定时任务)都必须经过 Worker 串行化,插件作者因此无需考虑并发。
5package plugin
6
7import (
8 "context"
9 "errors"
10 "fmt"
11 "sync"
12 "sync/atomic"
13 "time"
14)
15
16var errWorkerClosed = errors.New("插件运行时已关闭")
17
18type callReq struct {
19 fn func(*jsRuntime) (any, error)
20 resp chan callResp
21}
22
23type callResp struct {
24 val any
25 err error
26}
27
28// Worker 串行执行某个插件的全部脚本调用
29type Worker struct {
30 rt *jsRuntime
31 jobs chan *callReq
32 quit chan struct{}
33 closed atomic.Bool
34 once sync.Once
35}
36
37// NewWorker 创建并启动 worker(rt 已装配好 Host API)
38func NewWorker(rt *jsRuntime) *Worker {
39 w := &Worker{
40 rt: rt,
41 jobs: make(chan *callReq, 64),
42 quit: make(chan struct{}),
43 }
44 go w.loop()
45 return w
46}
47
48func (w *Worker) loop() {
49 for {
50 select {
51 case req := <-w.jobs:
52 w.exec(req)
53 case <-w.quit:
54 return
55 }
56 }
57}
58
59// exec 执行一次调用,保证异常不逃逸(panic 隔离)
60func (w *Worker) exec(req *callReq) {
61 defer func() {
62 if r := recover(); r != nil {
63 req.resp <- callResp{nil, fmt.Errorf("插件脚本异常: %v", r)}
64 }
65 // 清理可能的中断标记,确保后续调用可用
66 defer func() { _ = recover() }()
67 w.rt.vm.ClearInterrupt()
68 }()
69 val, err := req.fn(w.rt)
70 req.resp <- callResp{val, err}
71}
72
73// Do 提交一次脚本调用;ctx 超时会中断虚拟机并返回错误
74func (w *Worker) Do(ctx context.Context, fn func(*jsRuntime) (any, error)) (any, error) {
75 if w == nil || w.rt == nil {
76 return nil, errWorkerClosed
77 }
78 if w.closed.Load() {
79 return nil, errWorkerClosed
80 }
81 req := &callReq{fn: fn, resp: make(chan callResp, 1)}
82 select {
83 case w.jobs <- req:
84 case <-w.quit:
85 return nil, errWorkerClosed
86 case <-ctx.Done():
87 return nil, ctx.Err()
88 }
89 select {
90 case r := <-req.resp:
91 return r.val, r.err
92 case <-ctx.Done():
93 // 超时:中断正在执行的脚本,等 worker 回包后返回
94 w.rt.Stop()
95 select {
96 case <-req.resp:
97 case <-time.After(800 * time.Millisecond):
98 }
99 return nil, ctx.Err()
100 }
101}
102
103// Closed 是否已关闭
104func (w *Worker) Closed() bool { return w == nil || w.closed.Load() }
105
106// Close 关闭 worker(幂等)
107func (w *Worker) Close() {
108 if w == nil {
109 return
110 }
111 w.once.Do(func() {
112 w.closed.Store(true)
113 close(w.quit)
114 })
115}