镜像自地址
https://github.com/tuna/tunasync.git
已同步 2025-12-07 06:56:47 +00:00
feature(worker): job schedule
这个提交包含在:
@@ -25,6 +25,24 @@ type jobMessage struct {
|
|||||||
msg string
|
msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mirrorJob struct {
|
||||||
|
provider mirrorProvider
|
||||||
|
ctrlChan chan ctrlAction
|
||||||
|
enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMirrorJob(provider mirrorProvider) *mirrorJob {
|
||||||
|
return &mirrorJob{
|
||||||
|
provider: provider,
|
||||||
|
ctrlChan: make(chan ctrlAction, 1),
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mirrorJob) Name() string {
|
||||||
|
return m.provider.Name()
|
||||||
|
}
|
||||||
|
|
||||||
// runMirrorJob is the goroutine where syncing job runs in
|
// runMirrorJob is the goroutine where syncing job runs in
|
||||||
// arguments:
|
// arguments:
|
||||||
// provider: mirror provider object
|
// provider: mirror provider object
|
||||||
@@ -32,7 +50,9 @@ type jobMessage struct {
|
|||||||
// managerChan: push messages to the manager, this channel should have a larger buffer
|
// managerChan: push messages to the manager, this channel should have a larger buffer
|
||||||
// sempaphore: make sure the concurrent running syncing job won't explode
|
// sempaphore: make sure the concurrent running syncing job won't explode
|
||||||
// TODO: message struct for managerChan
|
// TODO: message struct for managerChan
|
||||||
func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerChan chan<- jobMessage, semaphore chan empty) error {
|
func (m *mirrorJob) Run(managerChan chan<- jobMessage, semaphore chan empty) error {
|
||||||
|
|
||||||
|
provider := m.provider
|
||||||
|
|
||||||
// to make code shorter
|
// to make code shorter
|
||||||
runHooks := func(Hooks []jobHook, action func(h jobHook) error, hookname string) error {
|
runHooks := func(Hooks []jobHook, action func(h jobHook) error, hookname string) error {
|
||||||
@@ -40,10 +60,10 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
if err := action(hook); err != nil {
|
if err := action(hook); err != nil {
|
||||||
logger.Error(
|
logger.Error(
|
||||||
"failed at %s hooks for %s: %s",
|
"failed at %s hooks for %s: %s",
|
||||||
hookname, provider.Name(), err.Error(),
|
hookname, m.Name(), err.Error(),
|
||||||
)
|
)
|
||||||
managerChan <- jobMessage{
|
managerChan <- jobMessage{
|
||||||
tunasync.Failed, provider.Name(),
|
tunasync.Failed, m.Name(),
|
||||||
fmt.Sprintf("error exec hook %s: %s", hookname, err.Error()),
|
fmt.Sprintf("error exec hook %s: %s", hookname, err.Error()),
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -55,8 +75,8 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
runJobWrapper := func(kill <-chan empty, jobDone chan<- empty) error {
|
runJobWrapper := func(kill <-chan empty, jobDone chan<- empty) error {
|
||||||
defer close(jobDone)
|
defer close(jobDone)
|
||||||
|
|
||||||
managerChan <- jobMessage{tunasync.PreSyncing, provider.Name(), ""}
|
managerChan <- jobMessage{tunasync.PreSyncing, m.Name(), ""}
|
||||||
logger.Info("start syncing: %s", provider.Name())
|
logger.Info("start syncing: %s", m.Name())
|
||||||
|
|
||||||
Hooks := provider.Hooks()
|
Hooks := provider.Hooks()
|
||||||
rHooks := []jobHook{}
|
rHooks := []jobHook{}
|
||||||
@@ -74,7 +94,7 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
stopASAP := false // stop job as soon as possible
|
stopASAP := false // stop job as soon as possible
|
||||||
|
|
||||||
if retry > 0 {
|
if retry > 0 {
|
||||||
logger.Info("retry syncing: %s, retry: %d", provider.Name(), retry)
|
logger.Info("retry syncing: %s, retry: %d", m.Name(), retry)
|
||||||
}
|
}
|
||||||
err := runHooks(Hooks, func(h jobHook) error { return h.preExec() }, "pre-exec")
|
err := runHooks(Hooks, func(h jobHook) error { return h.preExec() }, "pre-exec")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,12 +102,12 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start syncing
|
// start syncing
|
||||||
managerChan <- jobMessage{tunasync.Syncing, provider.Name(), ""}
|
managerChan <- jobMessage{tunasync.Syncing, m.Name(), ""}
|
||||||
err = provider.Start()
|
err = provider.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(
|
logger.Error(
|
||||||
"failed to start syncing job for %s: %s",
|
"failed to start syncing job for %s: %s",
|
||||||
provider.Name(), err.Error(),
|
m.Name(), err.Error(),
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -108,7 +128,7 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
stopASAP = true
|
stopASAP = true
|
||||||
err := provider.Terminate()
|
err := provider.Terminate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("failed to terminate provider %s: %s", provider.Name(), err.Error())
|
logger.Error("failed to terminate provider %s: %s", m.Name(), err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
syncErr = errors.New("killed by manager")
|
syncErr = errors.New("killed by manager")
|
||||||
@@ -122,8 +142,8 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
|
|
||||||
if syncErr == nil {
|
if syncErr == nil {
|
||||||
// syncing success
|
// syncing success
|
||||||
logger.Info("succeeded syncing %s", provider.Name())
|
logger.Info("succeeded syncing %s", m.Name())
|
||||||
managerChan <- jobMessage{tunasync.Success, provider.Name(), ""}
|
managerChan <- jobMessage{tunasync.Success, m.Name(), ""}
|
||||||
// post-success hooks
|
// post-success hooks
|
||||||
err := runHooks(rHooks, func(h jobHook) error { return h.postSuccess() }, "post-success")
|
err := runHooks(rHooks, func(h jobHook) error { return h.postSuccess() }, "post-success")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -134,8 +154,8 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
// syncing failed
|
// syncing failed
|
||||||
logger.Warning("failed syncing %s: %s", provider.Name(), syncErr.Error())
|
logger.Warning("failed syncing %s: %s", m.Name(), syncErr.Error())
|
||||||
managerChan <- jobMessage{tunasync.Failed, provider.Name(), syncErr.Error()}
|
managerChan <- jobMessage{tunasync.Failed, m.Name(), syncErr.Error()}
|
||||||
|
|
||||||
// post-fail hooks
|
// post-fail hooks
|
||||||
logger.Debug("post-fail hooks")
|
logger.Debug("post-fail hooks")
|
||||||
@@ -164,9 +184,8 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled := true // whether this job is stopped by the manager
|
|
||||||
for {
|
for {
|
||||||
if enabled {
|
if m.enabled {
|
||||||
kill := make(chan empty)
|
kill := make(chan empty)
|
||||||
jobDone := make(chan empty)
|
jobDone := make(chan empty)
|
||||||
go runJob(kill, jobDone)
|
go runJob(kill, jobDone)
|
||||||
@@ -175,10 +194,10 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
select {
|
select {
|
||||||
case <-jobDone:
|
case <-jobDone:
|
||||||
logger.Debug("job done")
|
logger.Debug("job done")
|
||||||
case ctrl := <-ctrlChan:
|
case ctrl := <-m.ctrlChan:
|
||||||
switch ctrl {
|
switch ctrl {
|
||||||
case jobStop:
|
case jobStop:
|
||||||
enabled = false
|
m.enabled = false
|
||||||
close(kill)
|
close(kill)
|
||||||
<-jobDone
|
<-jobDone
|
||||||
case jobDisable:
|
case jobDisable:
|
||||||
@@ -186,12 +205,12 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
<-jobDone
|
<-jobDone
|
||||||
return nil
|
return nil
|
||||||
case jobRestart:
|
case jobRestart:
|
||||||
enabled = true
|
m.enabled = true
|
||||||
close(kill)
|
close(kill)
|
||||||
<-jobDone
|
<-jobDone
|
||||||
continue
|
continue
|
||||||
case jobStart:
|
case jobStart:
|
||||||
enabled = true
|
m.enabled = true
|
||||||
goto _wait_for_job
|
goto _wait_for_job
|
||||||
default:
|
default:
|
||||||
// TODO: implement this
|
// TODO: implement this
|
||||||
@@ -201,16 +220,16 @@ func runMirrorJob(provider mirrorProvider, ctrlChan <-chan ctrlAction, managerCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrl := <-ctrlChan
|
ctrl := <-m.ctrlChan
|
||||||
switch ctrl {
|
switch ctrl {
|
||||||
case jobStop:
|
case jobStop:
|
||||||
enabled = false
|
m.enabled = false
|
||||||
case jobDisable:
|
case jobDisable:
|
||||||
return nil
|
return nil
|
||||||
case jobRestart:
|
case jobRestart:
|
||||||
enabled = true
|
m.enabled = true
|
||||||
case jobStart:
|
case jobStart:
|
||||||
enabled = true
|
m.enabled = true
|
||||||
default:
|
default:
|
||||||
// TODO
|
// TODO
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -63,11 +63,11 @@ func TestMirrorJob(t *testing.T) {
|
|||||||
So(readedScriptContent, ShouldResemble, []byte(scriptContent))
|
So(readedScriptContent, ShouldResemble, []byte(scriptContent))
|
||||||
|
|
||||||
Convey("If we let it run several times", func(ctx C) {
|
Convey("If we let it run several times", func(ctx C) {
|
||||||
ctrlChan := make(chan ctrlAction)
|
|
||||||
managerChan := make(chan jobMessage, 10)
|
managerChan := make(chan jobMessage, 10)
|
||||||
semaphore := make(chan empty, 1)
|
semaphore := make(chan empty, 1)
|
||||||
|
job := newMirrorJob(provider)
|
||||||
|
|
||||||
go runMirrorJob(provider, ctrlChan, managerChan, semaphore)
|
go job.Run(managerChan, semaphore)
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
msg := <-managerChan
|
msg := <-managerChan
|
||||||
So(msg.status, ShouldEqual, PreSyncing)
|
So(msg.status, ShouldEqual, PreSyncing)
|
||||||
@@ -78,7 +78,7 @@ func TestMirrorJob(t *testing.T) {
|
|||||||
loggedContent, err := ioutil.ReadFile(provider.LogFile())
|
loggedContent, err := ioutil.ReadFile(provider.LogFile())
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(string(loggedContent), ShouldEqual, exceptedOutput)
|
So(string(loggedContent), ShouldEqual, exceptedOutput)
|
||||||
ctrlChan <- jobStart
|
job.ctrlChan <- jobStart
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case msg := <-managerChan:
|
case msg := <-managerChan:
|
||||||
@@ -92,7 +92,7 @@ func TestMirrorJob(t *testing.T) {
|
|||||||
So(0, ShouldEqual, 1)
|
So(0, ShouldEqual, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrlChan <- jobDisable
|
job.ctrlChan <- jobDisable
|
||||||
select {
|
select {
|
||||||
case <-managerChan:
|
case <-managerChan:
|
||||||
So(0, ShouldEqual, 1) // made this fail
|
So(0, ShouldEqual, 1) // made this fail
|
||||||
@@ -112,12 +112,12 @@ echo $TUNASYNC_WORKING_DIR
|
|||||||
err = ioutil.WriteFile(scriptFile, []byte(scriptContent), 0755)
|
err = ioutil.WriteFile(scriptFile, []byte(scriptContent), 0755)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
ctrlChan := make(chan ctrlAction)
|
|
||||||
managerChan := make(chan jobMessage, 10)
|
managerChan := make(chan jobMessage, 10)
|
||||||
semaphore := make(chan empty, 1)
|
semaphore := make(chan empty, 1)
|
||||||
|
job := newMirrorJob(provider)
|
||||||
|
|
||||||
Convey("If we kill it", func(ctx C) {
|
Convey("If we kill it", func(ctx C) {
|
||||||
go runMirrorJob(provider, ctrlChan, managerChan, semaphore)
|
go job.Run(managerChan, semaphore)
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
msg := <-managerChan
|
msg := <-managerChan
|
||||||
@@ -125,7 +125,7 @@ echo $TUNASYNC_WORKING_DIR
|
|||||||
msg = <-managerChan
|
msg = <-managerChan
|
||||||
So(msg.status, ShouldEqual, Syncing)
|
So(msg.status, ShouldEqual, Syncing)
|
||||||
|
|
||||||
ctrlChan <- jobStop
|
job.ctrlChan <- jobStop
|
||||||
|
|
||||||
msg = <-managerChan
|
msg = <-managerChan
|
||||||
So(msg.status, ShouldEqual, Failed)
|
So(msg.status, ShouldEqual, Failed)
|
||||||
@@ -134,11 +134,12 @@ echo $TUNASYNC_WORKING_DIR
|
|||||||
loggedContent, err := ioutil.ReadFile(provider.LogFile())
|
loggedContent, err := ioutil.ReadFile(provider.LogFile())
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(string(loggedContent), ShouldEqual, exceptedOutput)
|
So(string(loggedContent), ShouldEqual, exceptedOutput)
|
||||||
ctrlChan <- jobDisable
|
job.ctrlChan <- jobDisable
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("If we don't kill it", func(ctx C) {
|
Convey("If we don't kill it", func(ctx C) {
|
||||||
go runMirrorJob(provider, ctrlChan, managerChan, semaphore)
|
go job.Run(managerChan, semaphore)
|
||||||
|
|
||||||
msg := <-managerChan
|
msg := <-managerChan
|
||||||
So(msg.status, ShouldEqual, PreSyncing)
|
So(msg.status, ShouldEqual, PreSyncing)
|
||||||
msg = <-managerChan
|
msg = <-managerChan
|
||||||
@@ -154,7 +155,7 @@ echo $TUNASYNC_WORKING_DIR
|
|||||||
loggedContent, err := ioutil.ReadFile(provider.LogFile())
|
loggedContent, err := ioutil.ReadFile(provider.LogFile())
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(string(loggedContent), ShouldEqual, exceptedOutput)
|
So(string(loggedContent), ShouldEqual, exceptedOutput)
|
||||||
ctrlChan <- jobDisable
|
job.ctrlChan <- jobDisable
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
17
worker/main.go
普通文件
17
worker/main.go
普通文件
@@ -0,0 +1,17 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// toplevel module for workers
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
for {
|
||||||
|
// if time.Now().After() {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
71
worker/schedule.go
普通文件
71
worker/schedule.go
普通文件
@@ -0,0 +1,71 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
// schedule queue for jobs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ryszard/goskiplist/skiplist"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scheduleQueue struct {
|
||||||
|
sync.Mutex
|
||||||
|
list *skiplist.SkipList
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeLessThan(l, r interface{}) bool {
|
||||||
|
tl := l.(time.Time)
|
||||||
|
tr := r.(time.Time)
|
||||||
|
return tl.Before(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newScheduleQueue() *scheduleQueue {
|
||||||
|
queue := new(scheduleQueue)
|
||||||
|
queue.list = skiplist.NewCustomMap(timeLessThan)
|
||||||
|
return queue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *scheduleQueue) AddJob(schedTime time.Time, job *mirrorJob) {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
q.list.Set(schedTime, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop out the first job if it's time to run it
|
||||||
|
func (q *scheduleQueue) Pop() *mirrorJob {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
first := q.list.SeekToFirst()
|
||||||
|
if first == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer first.Close()
|
||||||
|
|
||||||
|
t := first.Key().(time.Time)
|
||||||
|
if t.Before(time.Now()) {
|
||||||
|
job := first.Value().(*mirrorJob)
|
||||||
|
q.list.Delete(first.Key())
|
||||||
|
return job
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove job
|
||||||
|
func (q *scheduleQueue) Remove(name string) bool {
|
||||||
|
q.Lock()
|
||||||
|
defer q.Unlock()
|
||||||
|
|
||||||
|
cur := q.list.Iterator()
|
||||||
|
defer cur.Close()
|
||||||
|
|
||||||
|
for cur.Next() {
|
||||||
|
cj := cur.Value().(*mirrorJob)
|
||||||
|
if cj.Name() == name {
|
||||||
|
q.list.Delete(cur.Key())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
50
worker/schedule_test.go
普通文件
50
worker/schedule_test.go
普通文件
@@ -0,0 +1,50 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSchedule(t *testing.T) {
|
||||||
|
|
||||||
|
Convey("MirrorJobSchedule should work", t, func(ctx C) {
|
||||||
|
schedule := newScheduleQueue()
|
||||||
|
|
||||||
|
Convey("When poping on empty schedule", func() {
|
||||||
|
job := schedule.Pop()
|
||||||
|
So(job, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When adding some jobs", func() {
|
||||||
|
c := cmdConfig{
|
||||||
|
name: "schedule_test",
|
||||||
|
}
|
||||||
|
provider, _ := newCmdProvider(c)
|
||||||
|
job := newMirrorJob(provider)
|
||||||
|
sched := time.Now().Add(1 * time.Second)
|
||||||
|
|
||||||
|
schedule.AddJob(sched, job)
|
||||||
|
So(schedule.Pop(), ShouldBeNil)
|
||||||
|
time.Sleep(1200 * time.Millisecond)
|
||||||
|
So(schedule.Pop(), ShouldEqual, job)
|
||||||
|
|
||||||
|
})
|
||||||
|
Convey("When removing jobs", func() {
|
||||||
|
c := cmdConfig{
|
||||||
|
name: "schedule_test",
|
||||||
|
}
|
||||||
|
provider, _ := newCmdProvider(c)
|
||||||
|
job := newMirrorJob(provider)
|
||||||
|
sched := time.Now().Add(1 * time.Second)
|
||||||
|
|
||||||
|
schedule.AddJob(sched, job)
|
||||||
|
So(schedule.Remove("something"), ShouldBeFalse)
|
||||||
|
So(schedule.Remove("schedule_test"), ShouldBeTrue)
|
||||||
|
time.Sleep(1200 * time.Millisecond)
|
||||||
|
So(schedule.Pop(), ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
在新工单中引用
屏蔽一个用户