github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/cronjob/cron_job.go (about) 1 package cronjob 2 3 import ( 4 "context" 5 "encoding/json" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/go-co-op/gocron" 11 "github.com/pkg/errors" 12 13 "github.com/machinefi/w3bstream/pkg/depends/conf/logger" 14 "github.com/machinefi/w3bstream/pkg/depends/kit/logr" 15 "github.com/machinefi/w3bstream/pkg/models" 16 "github.com/machinefi/w3bstream/pkg/modules/event" 17 "github.com/machinefi/w3bstream/pkg/types" 18 ) 19 20 const ( 21 listIntervalSecond = 3 22 ) 23 24 type cronJob struct { 25 listIntervalSecond int 26 } 27 28 func (t *cronJob) run(ctx context.Context) { 29 ctx, l := logr.Start(ctx, "cronjob.run") 30 defer l.End() 31 32 s := gocron.NewScheduler(time.UTC) 33 s.TagsUnique() 34 35 if _, err := s.Every(t.listIntervalSecond).Seconds().Do(t.do, ctx, s); err != nil { 36 l.Error(errors.Wrap(err, "create cronjob main loop failed")) 37 } 38 s.StartAsync() 39 } 40 41 func (t *cronJob) do(ctx context.Context, s *gocron.Scheduler) { 42 ctx, l := logger.NewSpanContext(ctx, "cronjob.do") 43 defer l.End() 44 45 d := types.MustMgrDBExecutorFromContext(ctx) 46 m := &models.CronJob{} 47 48 cs, err := m.List(d, nil) 49 if err != nil { 50 l.Error(errors.Wrap(err, "list cronjob db failed")) 51 return 52 } 53 54 t.tidyCronJobs(ctx, s, cs) 55 56 for _, c := range cs { 57 if _, err := s.Cron(c.CronExpressions).Tag(c.CronJobID.String()).Do(t.sendEvent, ctx, c); err != nil { 58 if !strings.Contains(err.Error(), "non-unique tag") { 59 l.WithValues("cronJobID", c.CronJobID).Error(errors.Wrap(err, "create new cron job failed")) 60 } 61 } 62 } 63 } 64 65 func (t *cronJob) tidyCronJobs(ctx context.Context, s *gocron.Scheduler, cs []models.CronJob) { 66 ctx, l := logr.Start(ctx, "cronjob.tidyCronJobs") 67 defer l.End() 68 69 cronJobIDs := make(map[types.SFID]bool, len(cs)) 70 for _, c := range cs { 71 cronJobIDs[c.CronJobID] = true 72 } 73 for _, tag := range s.GetAllTags() { 74 id, err := strconv.ParseUint(tag, 10, 64) 75 if err != nil { 76 l.WithValues("tag", tag).Error(errors.Wrap(err, "parse tag to uint64 failed")) 77 continue 78 } 79 if !cronJobIDs[types.SFID(id)] { 80 if err := s.RemoveByTag(tag); err != nil { 81 l.WithValues("tag", tag).Error(errors.Wrap(err, "remove cron job failed")) 82 } else { 83 l.WithValues("tag", tag).Info("remove cron job success") 84 } 85 } 86 } 87 } 88 89 func (t *cronJob) sendEvent(ctx context.Context, c models.CronJob) { 90 ctx, l := logr.Start(ctx, "cronjob.sendEvent", "cronJobID", c.CronJobID) 91 defer l.End() 92 93 d := types.MustMgrDBExecutorFromContext(ctx) 94 95 m := &models.Project{RelProject: models.RelProject{ProjectID: c.ProjectID}} 96 if err := m.FetchByProjectID(d); err != nil { 97 l.Error(errors.Wrap(err, "get project failed")) 98 return 99 } 100 payload, err := json.Marshal(struct { 101 ID types.SFID 102 Timestamp time.Time 103 }{ 104 c.CronJobID, 105 time.Now(), 106 }) 107 if err != nil { 108 l.Error(errors.Wrap(err, "failed to marshal payload")) 109 return 110 } 111 112 ctx = types.WithProject(ctx, m) 113 if _, err = event.HandleEvent(ctx, c.EventType, payload); err != nil { 114 l.Error(errors.Wrap(err, "send event failed")) 115 } 116 } 117 118 func Run(ctx context.Context) { 119 c := &cronJob{ 120 listIntervalSecond: listIntervalSecond, 121 } 122 c.run(ctx) 123 }