1
This commit is contained in:
commit
70d5483fe1
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.*
|
||||
!.gitignore
|
||||
go.sum
|
||||
node_modules
|
||||
package.json
|
||||
env.yml
|
9
LICENSE
Normal file
9
LICENSE
Normal file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 apigo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
BIN
build/task_darwin_amd64
Normal file
BIN
build/task_darwin_amd64
Normal file
Binary file not shown.
74
data.go
Normal file
74
data.go
Normal file
@ -0,0 +1,74 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
func (obj *PluginObject) Get(k string) any {
|
||||
obj.taskDataLock.RLock()
|
||||
defer obj.taskDataLock.RUnlock()
|
||||
return obj.taskData[k]
|
||||
}
|
||||
|
||||
func (obj *PluginObject) Set(k string, v any) {
|
||||
obj.taskDataLock.Lock()
|
||||
defer obj.taskDataLock.Unlock()
|
||||
obj.taskData[k] = v
|
||||
}
|
||||
|
||||
func (obj *PluginObject) GetSet(k string, fn func(old any) any) {
|
||||
obj.taskDataLock.Lock()
|
||||
defer obj.taskDataLock.Unlock()
|
||||
obj.taskData[k] = fn(obj.taskData[k])
|
||||
}
|
||||
|
||||
func (obj *PluginObject) Remove(k string) {
|
||||
obj.taskDataLock.Lock()
|
||||
defer obj.taskDataLock.Unlock()
|
||||
delete(obj.taskData, k)
|
||||
}
|
||||
|
||||
func (obj *PluginObject) Push(k string, v any) {
|
||||
obj.taskListLock.Lock()
|
||||
defer obj.taskListLock.Unlock()
|
||||
list1 := obj.taskList[k]
|
||||
if list1 == nil {
|
||||
list1 = list.New()
|
||||
obj.taskList[k] = list1
|
||||
}
|
||||
obj.taskList[k].PushBack(v)
|
||||
}
|
||||
|
||||
func (obj *PluginObject) Pop(k string) any {
|
||||
obj.taskListLock.RLock()
|
||||
list1 := obj.taskList[k]
|
||||
obj.taskListLock.RUnlock()
|
||||
if list1 == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
obj.taskListLock.Lock()
|
||||
defer obj.taskListLock.Unlock()
|
||||
item := list1.Front()
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
v := obj.taskList[k].Remove(item)
|
||||
return v
|
||||
}
|
||||
|
||||
func (obj *PluginObject) CountList(k string) int {
|
||||
obj.taskListLock.RLock()
|
||||
defer obj.taskListLock.RUnlock()
|
||||
list1 := obj.taskList[k]
|
||||
if list1 == nil {
|
||||
return 0
|
||||
}
|
||||
return list1.Len()
|
||||
}
|
||||
|
||||
func (obj *PluginObject) RemoveList(k string) {
|
||||
obj.taskListLock.Lock()
|
||||
defer obj.taskListLock.Unlock()
|
||||
delete(obj.taskList, k)
|
||||
}
|
32
go.mod
Normal file
32
go.mod
Normal file
@ -0,0 +1,32 @@
|
||||
module apigo.cc/gojs/task
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
apigo.cc/gojs v0.0.17
|
||||
apigo.cc/gojs/console v0.0.2
|
||||
apigo.cc/gojs/file v0.0.4
|
||||
apigo.cc/gojs/runtime v0.0.3
|
||||
apigo.cc/gojs/util v0.0.11
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/ssgo/log v1.7.7
|
||||
github.com/ssgo/u v1.7.20
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ZZMarquis/gm v1.3.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/emmansun/gmsm v0.30.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/obscuren/ecies v0.0.0-20150213224233-7c0f4a9b18d9 // indirect
|
||||
github.com/ssgo/config v1.7.9 // indirect
|
||||
github.com/ssgo/standard v1.7.7 // indirect
|
||||
github.com/ssgo/tool v0.4.29 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
48
plugin.go
Normal file
48
plugin.go
Normal file
@ -0,0 +1,48 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"apigo.cc/gojs"
|
||||
"apigo.cc/gojs/goja"
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
const pluginName = "task"
|
||||
|
||||
type PluginObject struct {
|
||||
skipCron *cron.Cron
|
||||
delayCron *cron.Cron
|
||||
// asyncCron *cron.Cron
|
||||
stopChan chan struct{}
|
||||
isStarted bool
|
||||
lock sync.RWMutex
|
||||
tasks map[string]*Task
|
||||
taskData map[string]any
|
||||
taskDataLock sync.RWMutex
|
||||
|
||||
taskList map[string]*list.List
|
||||
taskListLock sync.RWMutex
|
||||
}
|
||||
|
||||
var _pluginObject = &PluginObject{
|
||||
tasks: map[string]*Task{},
|
||||
taskData: map[string]any{},
|
||||
taskList: map[string]*list.List{},
|
||||
}
|
||||
|
||||
func init() {
|
||||
tsCode := gojs.MakeTSCode(_pluginObject)
|
||||
mappedObj := gojs.ToMap(_pluginObject)
|
||||
gojs.Register("apigo.cc/gojs/"+pluginName, gojs.Module{
|
||||
ObjectMaker: func(vm *goja.Runtime) gojs.Map {
|
||||
return mappedObj
|
||||
},
|
||||
OnKill: func() {
|
||||
_pluginObject.Stop()
|
||||
},
|
||||
TsCode: tsCode,
|
||||
Desc: "task api",
|
||||
})
|
||||
}
|
30
plugin_test.go
Normal file
30
plugin_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package plugin_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"apigo.cc/gojs"
|
||||
_ "apigo.cc/gojs/console"
|
||||
_ "apigo.cc/gojs/file"
|
||||
_ "apigo.cc/gojs/runtime"
|
||||
_ "apigo.cc/gojs/util"
|
||||
"github.com/ssgo/u"
|
||||
)
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
gojs.ExportForDev()
|
||||
for _, f := range u.ReadDirN(".") {
|
||||
if strings.HasSuffix(f.Name, "_test.js") {
|
||||
r, err := gojs.RunFile(f.Name)
|
||||
if err != nil {
|
||||
t.Fatal(u.Red(f.Name), u.BRed(err.Error()))
|
||||
} else if r != true {
|
||||
t.Fatal(u.Red(f.Name), u.BRed(u.JsonP(r)))
|
||||
} else {
|
||||
fmt.Println(u.Green(f.Name), u.BGreen("test succeess"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
plugin_test.js
Normal file
44
plugin_test.js
Normal file
@ -0,0 +1,44 @@
|
||||
import task from 'apigo.cc/gojs/task'
|
||||
import rt from 'apigo.cc/gojs/runtime'
|
||||
import co from 'apigo.cc/gojs/console'
|
||||
import file from 'apigo.cc/gojs/file'
|
||||
import u from 'apigo.cc/gojs/util'
|
||||
|
||||
co.info('plugin test start')
|
||||
try {
|
||||
file.mkdir('testTasks/tmp')
|
||||
task.asyncStart()
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let jsFileA = 'testTasks/tmp/a' + i + '.js'
|
||||
let jsFileB = 'testTasks/tmp/b' + i + '.js'
|
||||
file.copy('testTasks/a.js', jsFileA)
|
||||
file.copy('testTasks/b.js', jsFileB)
|
||||
task.newTask('@every 1s', jsFileA)
|
||||
task.newTask('@every 1s', jsFileB)
|
||||
}
|
||||
|
||||
rt.sleep(3000)
|
||||
task.stop()
|
||||
|
||||
let aStarts = task.get('aStarts')
|
||||
if (aStarts !== 10) return 'aStarts(' + aStarts + ') not 10'
|
||||
let aStops = task.get('aStops')
|
||||
if (aStops !== 10) return 'aStops(' + aStops + ') not 10'
|
||||
let bStarts = task.get('bStarts')
|
||||
if (bStarts !== 10) return 'bStarts(' + bStarts + ') not 10'
|
||||
let bStops = task.get('bStops')
|
||||
if (bStops !== 10) return 'bStops(' + bStops + ') not 10'
|
||||
let aRunTimes = task.get('aRunTimes')
|
||||
if (aRunTimes !== 30) return 'aRunTimes(' + aRunTimes + ') not 30'
|
||||
|
||||
co.info()
|
||||
|
||||
return true
|
||||
} catch (ex) {
|
||||
co.error(ex)
|
||||
return false
|
||||
} finally {
|
||||
file.remove('testTasks/tmp')
|
||||
co.info('plugin test end')
|
||||
}
|
176
task.go
Normal file
176
task.go
Normal file
@ -0,0 +1,176 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"apigo.cc/gojs"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/ssgo/log"
|
||||
"github.com/ssgo/u"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
file string
|
||||
vm *gojs.Runtime
|
||||
lock sync.RWMutex
|
||||
mtime time.Time
|
||||
entryId cron.EntryID
|
||||
policy string
|
||||
}
|
||||
|
||||
func (obj *PluginObject) AsyncStart() {
|
||||
obj.lock.Lock()
|
||||
defer obj.lock.Unlock()
|
||||
if !obj.isStarted {
|
||||
obj.skipCron = cron.New(cron.WithSeconds(), cron.WithChain(cron.SkipIfStillRunning(cron.DefaultLogger)))
|
||||
obj.skipCron.Start()
|
||||
obj.delayCron = cron.New(cron.WithSeconds())
|
||||
obj.delayCron.Start()
|
||||
// obj.asyncCron = cron.New(cron.WithSeconds())
|
||||
// obj.asyncCron.Start()
|
||||
obj.isStarted = true
|
||||
obj.stopChan = make(chan struct{})
|
||||
obj.skipCron.AddFunc("@every 5s", func() {
|
||||
tasks := []*Task{}
|
||||
obj.lock.RLock()
|
||||
for _, task := range obj.tasks {
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
obj.lock.RUnlock()
|
||||
|
||||
for _, task := range tasks {
|
||||
if fi := u.GetFileInfo(task.file); fi != nil {
|
||||
if !fi.ModTime.Equal(task.mtime) {
|
||||
// 文件变化,重新加载
|
||||
rt := gojs.New()
|
||||
if _, err := rt.RunFile(task.file); err == nil {
|
||||
task.lock.Lock()
|
||||
task.vm = rt
|
||||
task.mtime = fi.ModTime
|
||||
task.lock.Unlock()
|
||||
} else {
|
||||
// 发生错误时仅更新 mtime,防止不断重新加载
|
||||
log.DefaultLogger.Error("failed to reload task code", "err", err.Error(), "file", task.file)
|
||||
task.lock.Lock()
|
||||
task.mtime = fi.ModTime
|
||||
task.lock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (obj *PluginObject) Start() {
|
||||
obj.AsyncStart()
|
||||
<-obj.stopChan
|
||||
}
|
||||
|
||||
func (obj *PluginObject) Stop() {
|
||||
obj.lock.RLock()
|
||||
isStarted := obj.isStarted
|
||||
obj.lock.RUnlock()
|
||||
if !isStarted {
|
||||
return
|
||||
}
|
||||
defer close(obj.stopChan)
|
||||
ctx1 := obj.skipCron.Stop()
|
||||
ctx2 := obj.delayCron.Stop()
|
||||
// ctx3 := obj.asyncCron.Stop()
|
||||
<-ctx1.Done()
|
||||
<-ctx2.Done()
|
||||
// <-ctx3.Done()
|
||||
|
||||
obj.lock.Lock()
|
||||
defer obj.lock.Unlock()
|
||||
for _, task := range obj.tasks {
|
||||
task.RunFunc("onStop")
|
||||
}
|
||||
|
||||
obj.isStarted = false
|
||||
obj.tasks = map[string]*Task{}
|
||||
}
|
||||
|
||||
func (obj *PluginObject) NewTask(spec string, file string, policy *string) error {
|
||||
fi := u.GetFileInfo(file)
|
||||
if fi == nil {
|
||||
return u.Error("task file not exists")
|
||||
}
|
||||
|
||||
rt := gojs.New()
|
||||
if _, err := rt.RunFile(file); err != nil {
|
||||
return u.Error(err.Error())
|
||||
}
|
||||
|
||||
task := &Task{
|
||||
file: file,
|
||||
vm: rt,
|
||||
mtime: fi.ModTime,
|
||||
policy: u.String(policy),
|
||||
}
|
||||
if err := task.RunFunc("onStart"); err != nil {
|
||||
return u.Error(err.Error())
|
||||
}
|
||||
|
||||
var err error
|
||||
switch task.policy {
|
||||
case "skip":
|
||||
task.entryId, err = obj.skipCron.AddFunc(spec, func() {
|
||||
task.RunFunc("onRun")
|
||||
})
|
||||
// case "async":
|
||||
// task.entryId, err = obj.asyncCron.AddFunc(spec, func() {
|
||||
// go func() {
|
||||
// task.RunFunc("onRun")
|
||||
// }()
|
||||
// })
|
||||
default:
|
||||
task.policy = "delay"
|
||||
task.entryId, err = obj.delayCron.AddFunc(spec, func() {
|
||||
task.RunFunc("onRun")
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return u.Error(err.Error())
|
||||
}
|
||||
|
||||
obj.lock.Lock()
|
||||
defer obj.lock.Unlock()
|
||||
obj.tasks[spec+file] = task
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *PluginObject) RemoveTask(spec string, file string) error {
|
||||
obj.lock.RLock()
|
||||
task := obj.tasks[spec+file]
|
||||
obj.lock.RUnlock()
|
||||
if task == nil {
|
||||
return nil
|
||||
}
|
||||
task.RunFunc("onStop")
|
||||
switch task.policy {
|
||||
case "skip":
|
||||
obj.skipCron.Remove(task.entryId)
|
||||
// case "async":
|
||||
// obj.asyncCron.Remove(task.entryId)
|
||||
default:
|
||||
obj.delayCron.Remove(task.entryId)
|
||||
}
|
||||
|
||||
obj.lock.Lock()
|
||||
defer obj.lock.Unlock()
|
||||
delete(obj.tasks, spec+file)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (task *Task) RunFunc(fnName string) error {
|
||||
task.lock.RLock()
|
||||
rt := task.vm
|
||||
task.lock.RUnlock()
|
||||
_, err := rt.RunCode(fmt.Sprintf("if(%s)%s()", fnName, fnName))
|
||||
return err
|
||||
}
|
34
testTasks/a.js
Normal file
34
testTasks/a.js
Normal file
@ -0,0 +1,34 @@
|
||||
import rt from 'apigo.cc/gojs/runtime'
|
||||
import co from 'apigo.cc/gojs/console'
|
||||
import task from 'apigo.cc/gojs/task'
|
||||
import u from 'apigo.cc/gojs/util'
|
||||
|
||||
const taskName = '「a」'
|
||||
let runTimes = 0
|
||||
// let t1
|
||||
|
||||
function onStart() {
|
||||
// co.info(taskName, 'start')
|
||||
task.getSet('aStarts', old => {
|
||||
return u.int(old) + 1
|
||||
})
|
||||
// t1 = u.timestampMS()
|
||||
}
|
||||
|
||||
let i = 0
|
||||
function onRun() {
|
||||
// co.info(taskName, 'run======================', runTimes, u.timestampMS() - t1)
|
||||
runTimes++
|
||||
task.push('aRunTime', runTimes)
|
||||
task.getSet('aRunTimes', old => {
|
||||
return u.int(old) + 1
|
||||
})
|
||||
// rt.sleep(1500)
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
// co.info(taskName, 'stop')
|
||||
task.getSet('aStops', old => {
|
||||
return u.int(old) + 1
|
||||
})
|
||||
}
|
33
testTasks/b.js
Normal file
33
testTasks/b.js
Normal file
@ -0,0 +1,33 @@
|
||||
import rt from 'apigo.cc/gojs/runtime'
|
||||
import co from 'apigo.cc/gojs/console'
|
||||
import task from 'apigo.cc/gojs/task'
|
||||
import u from 'apigo.cc/gojs/util'
|
||||
|
||||
const taskName = '「b」'
|
||||
let runTimes = 0
|
||||
|
||||
function onStart() {
|
||||
// co.info(taskName, 'start')
|
||||
task.getSet('bStarts', old => {
|
||||
return u.int(old) + 1
|
||||
})
|
||||
}
|
||||
|
||||
let i = 0
|
||||
function onRun() {
|
||||
runTimes++
|
||||
// co.info(taskName, 'run start', runTimes)
|
||||
rt.sleep(200)
|
||||
let v = task.pop('aRunTime')
|
||||
task.getSet('Count' + u.string(v), old => {
|
||||
return u.int(old) + 1
|
||||
})
|
||||
// co.info(taskName, 'run end', runTimes)
|
||||
}
|
||||
|
||||
function onStop() {
|
||||
// co.info(taskName, 'stop')
|
||||
task.getSet('bStops', old => {
|
||||
return u.int(old) + 1
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user