job.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package worker
  2. import (
  3. "errors"
  4. "fmt"
  5. tunasync "github.com/tuna/tunasync/internal"
  6. )
  7. // this file contains the workflow of a mirror jb
  8. type ctrlAction uint8
  9. const (
  10. jobStart ctrlAction = iota
  11. jobStop // stop syncing keep the job
  12. jobDisable // disable the job (stops goroutine)
  13. jobRestart // restart syncing
  14. jobPing // ensure the goroutine is alive
  15. )
  16. type jobMessage struct {
  17. status tunasync.SyncStatus
  18. name string
  19. msg string
  20. }
  21. type mirrorJob struct {
  22. provider mirrorProvider
  23. ctrlChan chan ctrlAction
  24. enabled bool
  25. }
  26. func newMirrorJob(provider mirrorProvider) *mirrorJob {
  27. return &mirrorJob{
  28. provider: provider,
  29. ctrlChan: make(chan ctrlAction, 1),
  30. enabled: true,
  31. }
  32. }
  33. func (m *mirrorJob) Name() string {
  34. return m.provider.Name()
  35. }
  36. // runMirrorJob is the goroutine where syncing job runs in
  37. // arguments:
  38. // provider: mirror provider object
  39. // ctrlChan: receives messages from the manager
  40. // managerChan: push messages to the manager, this channel should have a larger buffer
  41. // sempaphore: make sure the concurrent running syncing job won't explode
  42. // TODO: message struct for managerChan
  43. func (m *mirrorJob) Run(managerChan chan<- jobMessage, semaphore chan empty) error {
  44. provider := m.provider
  45. // to make code shorter
  46. runHooks := func(Hooks []jobHook, action func(h jobHook) error, hookname string) error {
  47. for _, hook := range Hooks {
  48. if err := action(hook); err != nil {
  49. logger.Error(
  50. "failed at %s hooks for %s: %s",
  51. hookname, m.Name(), err.Error(),
  52. )
  53. managerChan <- jobMessage{
  54. tunasync.Failed, m.Name(),
  55. fmt.Sprintf("error exec hook %s: %s", hookname, err.Error()),
  56. }
  57. return err
  58. }
  59. }
  60. return nil
  61. }
  62. runJobWrapper := func(kill <-chan empty, jobDone chan<- empty) error {
  63. defer close(jobDone)
  64. managerChan <- jobMessage{tunasync.PreSyncing, m.Name(), ""}
  65. logger.Info("start syncing: %s", m.Name())
  66. Hooks := provider.Hooks()
  67. rHooks := []jobHook{}
  68. for i := len(Hooks); i > 0; i-- {
  69. rHooks = append(rHooks, Hooks[i-1])
  70. }
  71. logger.Debug("hooks: pre-job")
  72. err := runHooks(Hooks, func(h jobHook) error { return h.preJob() }, "pre-job")
  73. if err != nil {
  74. return err
  75. }
  76. for retry := 0; retry < maxRetry; retry++ {
  77. stopASAP := false // stop job as soon as possible
  78. if retry > 0 {
  79. logger.Info("retry syncing: %s, retry: %d", m.Name(), retry)
  80. }
  81. err := runHooks(Hooks, func(h jobHook) error { return h.preExec() }, "pre-exec")
  82. if err != nil {
  83. return err
  84. }
  85. // start syncing
  86. managerChan <- jobMessage{tunasync.Syncing, m.Name(), ""}
  87. err = provider.Start()
  88. if err != nil {
  89. logger.Error(
  90. "failed to start syncing job for %s: %s",
  91. m.Name(), err.Error(),
  92. )
  93. return err
  94. }
  95. var syncErr error
  96. syncDone := make(chan error, 1)
  97. go func() {
  98. err := provider.Wait()
  99. if !stopASAP {
  100. syncDone <- err
  101. }
  102. }()
  103. select {
  104. case syncErr = <-syncDone:
  105. logger.Debug("syncing done")
  106. case <-kill:
  107. logger.Debug("received kill")
  108. stopASAP = true
  109. err := provider.Terminate()
  110. if err != nil {
  111. logger.Error("failed to terminate provider %s: %s", m.Name(), err.Error())
  112. return err
  113. }
  114. syncErr = errors.New("killed by manager")
  115. }
  116. // post-exec hooks
  117. herr := runHooks(rHooks, func(h jobHook) error { return h.postExec() }, "post-exec")
  118. if herr != nil {
  119. return herr
  120. }
  121. if syncErr == nil {
  122. // syncing success
  123. logger.Info("succeeded syncing %s", m.Name())
  124. managerChan <- jobMessage{tunasync.Success, m.Name(), ""}
  125. // post-success hooks
  126. err := runHooks(rHooks, func(h jobHook) error { return h.postSuccess() }, "post-success")
  127. if err != nil {
  128. return err
  129. }
  130. return nil
  131. }
  132. // syncing failed
  133. logger.Warning("failed syncing %s: %s", m.Name(), syncErr.Error())
  134. managerChan <- jobMessage{tunasync.Failed, m.Name(), syncErr.Error()}
  135. // post-fail hooks
  136. logger.Debug("post-fail hooks")
  137. err = runHooks(rHooks, func(h jobHook) error { return h.postFail() }, "post-fail")
  138. if err != nil {
  139. return err
  140. }
  141. // gracefully exit
  142. if stopASAP {
  143. logger.Debug("No retry, exit directly")
  144. return nil
  145. }
  146. // continue to next retry
  147. } // for retry
  148. return nil
  149. }
  150. runJob := func(kill <-chan empty, jobDone chan<- empty) {
  151. select {
  152. case semaphore <- empty{}:
  153. defer func() { <-semaphore }()
  154. runJobWrapper(kill, jobDone)
  155. case <-kill:
  156. jobDone <- empty{}
  157. return
  158. }
  159. }
  160. for {
  161. if m.enabled {
  162. kill := make(chan empty)
  163. jobDone := make(chan empty)
  164. go runJob(kill, jobDone)
  165. _wait_for_job:
  166. select {
  167. case <-jobDone:
  168. logger.Debug("job done")
  169. case ctrl := <-m.ctrlChan:
  170. switch ctrl {
  171. case jobStop:
  172. m.enabled = false
  173. close(kill)
  174. <-jobDone
  175. case jobDisable:
  176. close(kill)
  177. <-jobDone
  178. return nil
  179. case jobRestart:
  180. m.enabled = true
  181. close(kill)
  182. <-jobDone
  183. continue
  184. case jobStart:
  185. m.enabled = true
  186. goto _wait_for_job
  187. default:
  188. // TODO: implement this
  189. close(kill)
  190. return nil
  191. }
  192. }
  193. }
  194. ctrl := <-m.ctrlChan
  195. switch ctrl {
  196. case jobStop:
  197. m.enabled = false
  198. case jobDisable:
  199. return nil
  200. case jobRestart:
  201. m.enabled = true
  202. case jobStart:
  203. m.enabled = true
  204. default:
  205. // TODO
  206. return nil
  207. }
  208. }
  209. }