refactor(worker): use atomic state to simplify job control

This commit is contained in:
bigeagle 2016-04-29 13:52:58 +08:00
parent 2268eb3b0f
commit 41e1f263a5
No known key found for this signature in database
GPG Key ID: 9171A4571C27920A
2 changed files with 58 additions and 51 deletions

View File

@ -3,6 +3,7 @@ package worker
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync/atomic"
tunasync "github.com/tuna/tunasync/internal" tunasync "github.com/tuna/tunasync/internal"
) )
@ -26,22 +27,29 @@ type jobMessage struct {
schedule bool schedule bool
} }
const (
// empty state
stateNone uint32 = iota
// ready to run, able to schedule
stateReady
// paused by jobStop
statePaused
// disabled by jobDisable
stateDisabled
)
type mirrorJob struct { type mirrorJob struct {
provider mirrorProvider provider mirrorProvider
ctrlChan chan ctrlAction ctrlChan chan ctrlAction
disabled chan empty disabled chan empty
started bool state uint32
schedule bool
isDisabled bool
} }
func newMirrorJob(provider mirrorProvider) *mirrorJob { func newMirrorJob(provider mirrorProvider) *mirrorJob {
return &mirrorJob{ return &mirrorJob{
provider: provider, provider: provider,
ctrlChan: make(chan ctrlAction, 1), ctrlChan: make(chan ctrlAction, 1),
started: false, state: stateNone,
schedule: false,
isDisabled: false,
} }
} }
@ -49,6 +57,14 @@ func (m *mirrorJob) Name() string {
return m.provider.Name() return m.provider.Name()
} }
func (m *mirrorJob) State() uint32 {
return atomic.LoadUint32(&(m.state))
}
func (m *mirrorJob) SetState(state uint32) {
atomic.StoreUint32(&(m.state), state)
}
// 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
@ -61,8 +77,7 @@ func (m *mirrorJob) Run(managerChan chan<- jobMessage, semaphore chan empty) err
m.disabled = make(chan empty) m.disabled = make(chan empty)
defer func() { defer func() {
close(m.disabled) close(m.disabled)
m.schedule = false m.SetState(stateDisabled)
m.isDisabled = true
}() }()
provider := m.provider provider := m.provider
@ -192,7 +207,7 @@ func (m *mirrorJob) Run(managerChan chan<- jobMessage, semaphore chan empty) err
} }
for { for {
if m.started { if m.State() == stateReady {
kill := make(chan empty) kill := make(chan empty)
jobDone := make(chan empty) jobDone := make(chan empty)
go runJob(kill, jobDone) go runJob(kill, jobDone)
@ -204,24 +219,21 @@ func (m *mirrorJob) Run(managerChan chan<- jobMessage, semaphore chan empty) err
case ctrl := <-m.ctrlChan: case ctrl := <-m.ctrlChan:
switch ctrl { switch ctrl {
case jobStop: case jobStop:
m.schedule = false m.SetState(statePaused)
m.started = false
close(kill) close(kill)
<-jobDone <-jobDone
case jobDisable: case jobDisable:
m.schedule = false m.SetState(stateDisabled)
m.isDisabled = true
m.started = false
close(kill) close(kill)
<-jobDone <-jobDone
return nil return nil
case jobRestart: case jobRestart:
m.started = true m.SetState(stateReady)
close(kill) close(kill)
<-jobDone <-jobDone
continue continue
case jobStart: case jobStart:
m.started = true m.SetState(stateReady)
goto _wait_for_job goto _wait_for_job
default: default:
// TODO: implement this // TODO: implement this
@ -234,21 +246,14 @@ func (m *mirrorJob) Run(managerChan chan<- jobMessage, semaphore chan empty) err
ctrl := <-m.ctrlChan ctrl := <-m.ctrlChan
switch ctrl { switch ctrl {
case jobStop: case jobStop:
m.schedule = false m.SetState(statePaused)
m.started = false
case jobDisable: case jobDisable:
m.schedule = false m.SetState(stateDisabled)
m.isDisabled = true
m.started = false
return nil return nil
case jobRestart: case jobRestart:
m.schedule = true m.SetState(stateReady)
m.isDisabled = false
m.started = true
case jobStart: case jobStart:
m.schedule = true m.SetState(stateReady)
m.isDisabled = false
m.started = true
default: default:
// TODO // TODO
return nil return nil

View File

@ -186,32 +186,24 @@ func (w *Worker) makeHTTPServer() {
// if job disabled, start them first // if job disabled, start them first
switch cmd.Cmd { switch cmd.Cmd {
case CmdStart, CmdRestart: case CmdStart, CmdRestart:
if job.isDisabled { if job.State() == stateDisabled {
go job.Run(w.managerChan, w.semaphore) go job.Run(w.managerChan, w.semaphore)
} }
} }
switch cmd.Cmd { switch cmd.Cmd {
case CmdStart: case CmdStart:
job.schedule = true
job.isDisabled = false
job.ctrlChan <- jobStart job.ctrlChan <- jobStart
case CmdRestart: case CmdRestart:
job.schedule = true
job.isDisabled = false
job.ctrlChan <- jobRestart job.ctrlChan <- jobRestart
case CmdStop: case CmdStop:
// if job is disabled, no goroutine would be there // if job is disabled, no goroutine would be there
// receiving this signal // receiving this signal
if !job.isDisabled { if job.State() != stateDisabled {
job.schedule = false
job.isDisabled = false
w.schedule.Remove(job.Name()) w.schedule.Remove(job.Name())
job.ctrlChan <- jobStop job.ctrlChan <- jobStop
} }
case CmdDisable: case CmdDisable:
if !job.isDisabled { if job.State() != stateDisabled {
job.schedule = false
job.isDisabled = true
w.schedule.Remove(job.Name()) w.schedule.Remove(job.Name())
job.ctrlChan <- jobDisable job.ctrlChan <- jobDisable
<-job.disabled <-job.disabled
@ -270,15 +262,15 @@ func (w *Worker) runSchedule() {
if job, ok := w.jobs[m.Name]; ok { if job, ok := w.jobs[m.Name]; ok {
delete(unset, m.Name) delete(unset, m.Name)
switch m.Status { switch m.Status {
case Paused:
go job.Run(w.managerChan, w.semaphore)
job.schedule = false
continue
case Disabled: case Disabled:
job.schedule = false job.SetState(stateDisabled)
job.isDisabled = true continue
case Paused:
job.SetState(statePaused)
go job.Run(w.managerChan, w.semaphore)
continue continue
default: default:
job.SetState(stateReady)
go job.Run(w.managerChan, w.semaphore) go job.Run(w.managerChan, w.semaphore)
stime := m.LastUpdate.Add(job.provider.Interval()) stime := m.LastUpdate.Add(job.provider.Interval())
logger.Debug("Scheduling job %s @%s", job.Name(), stime.Format("2006-01-02 15:04:05")) logger.Debug("Scheduling job %s @%s", job.Name(), stime.Format("2006-01-02 15:04:05"))
@ -286,8 +278,12 @@ func (w *Worker) runSchedule() {
} }
} }
} }
// some new jobs may be added
// which does not exist in the
// manager's mirror list
for name := range unset { for name := range unset {
job := w.jobs[name] job := w.jobs[name]
job.SetState(stateReady)
go job.Run(w.managerChan, w.semaphore) go job.Run(w.managerChan, w.semaphore)
w.schedule.AddJob(time.Now(), job) w.schedule.AddJob(time.Now(), job)
} }
@ -297,13 +293,19 @@ func (w *Worker) runSchedule() {
case jobMsg := <-w.managerChan: case jobMsg := <-w.managerChan:
// got status update from job // got status update from job
job := w.jobs[jobMsg.name] job := w.jobs[jobMsg.name]
if !job.schedule { if job.State() != stateReady {
logger.Info("Job %s disabled/paused, skip adding new schedule", jobMsg.name) logger.Info("Job %s state is not ready, skip adding new schedule", jobMsg.name)
continue continue
} }
// syncing status is only meaningful when job
// is running. If it's paused or disabled
// a sync failure signal would be emitted
// which needs to be ignored
w.updateStatus(jobMsg) w.updateStatus(jobMsg)
// only successful or the final failure msg
// can trigger scheduling
if jobMsg.schedule { if jobMsg.schedule {
schedTime := time.Now().Add(job.provider.Interval()) schedTime := time.Now().Add(job.provider.Interval())
logger.Info( logger.Info(