github.com/matrixorigin/matrixone@v0.7.0/pkg/taskservice/task_service.go (about) 1 // Copyright 2022 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package taskservice 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 "time" 22 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 "github.com/matrixorigin/matrixone/pkg/common/runtime" 25 "github.com/matrixorigin/matrixone/pkg/common/stopper" 26 "github.com/matrixorigin/matrixone/pkg/pb/task" 27 "github.com/robfig/cron/v3" 28 ) 29 30 type taskService struct { 31 store TaskStorage 32 cronParser cron.Parser 33 rt runtime.Runtime 34 35 crons struct { 36 sync.Mutex 37 38 started bool 39 stopping bool 40 stopper *stopper.Stopper 41 cron *cron.Cron 42 retryC chan task.CronTask 43 jobs map[uint64]*cronJob 44 entryIDs map[uint64]cron.EntryID 45 } 46 } 47 48 // NewTaskService create a task service based on a task storage. 49 func NewTaskService( 50 rt runtime.Runtime, 51 store TaskStorage) TaskService { 52 return &taskService{ 53 rt: rt, 54 store: store, 55 cronParser: cron.NewParser( 56 cron.Second | 57 cron.Minute | 58 cron.Hour | 59 cron.Dom | 60 cron.Month | 61 cron.Dow | 62 cron.Descriptor), 63 } 64 } 65 66 func (s *taskService) Create(ctx context.Context, value task.TaskMetadata) error { 67 for { 68 select { 69 case <-ctx.Done(): 70 s.rt.Logger().Error("create task timeout") 71 return errNotReady 72 default: 73 if _, err := s.store.Add(ctx, newTaskFromMetadata(value)); err != nil { 74 if err == errNotReady { 75 time.Sleep(300 * time.Millisecond) 76 continue 77 } 78 return err 79 } 80 return nil 81 } 82 } 83 } 84 85 func (s *taskService) CreateBatch(ctx context.Context, tasks []task.TaskMetadata) error { 86 values := make([]task.Task, 0, len(tasks)) 87 for _, v := range tasks { 88 values = append(values, newTaskFromMetadata(v)) 89 } 90 91 for { 92 select { 93 case <-ctx.Done(): 94 s.rt.Logger().Error("create task timeout") 95 return errNotReady 96 default: 97 if _, err := s.store.Add(ctx, values...); err != nil { 98 if err == errNotReady { 99 time.Sleep(300 * time.Millisecond) 100 continue 101 } 102 return err 103 } 104 return nil 105 } 106 } 107 } 108 109 func (s *taskService) CreateCronTask(ctx context.Context, value task.TaskMetadata, cronExpr string) error { 110 sche, err := s.cronParser.Parse(cronExpr) 111 if err != nil { 112 return err 113 } 114 115 now := time.Now().UnixMilli() 116 next := sche.Next(time.UnixMilli(now)) 117 118 _, err = s.store.AddCronTask(ctx, task.CronTask{ 119 Metadata: value, 120 CronExpr: cronExpr, 121 NextTime: next.UnixMilli(), 122 TriggerTimes: 0, 123 CreateAt: now, 124 UpdateAt: now, 125 }) 126 return err 127 } 128 129 func (s *taskService) Allocate(ctx context.Context, value task.Task, taskRunner string) error { 130 exists, err := s.store.Query(ctx, WithTaskIDCond(EQ, value.ID)) 131 if err != nil { 132 return err 133 } 134 if len(exists) != 1 { 135 s.rt.Logger().Debug(fmt.Sprintf("queried tasks: %v", exists)) 136 s.rt.Logger().Fatal(fmt.Sprintf("query task by primary key, return %d records", len(exists))) 137 } 138 139 old := exists[0] 140 switch old.Status { 141 case task.TaskStatus_Running: 142 old.Epoch++ 143 old.TaskRunner = taskRunner 144 old.LastHeartbeat = time.Now().UnixMilli() 145 case task.TaskStatus_Created: 146 old.Status = task.TaskStatus_Running 147 old.Epoch = 1 148 old.TaskRunner = taskRunner 149 old.LastHeartbeat = time.Now().UnixMilli() 150 default: 151 return moerr.NewInvalidTask(ctx, taskRunner, value.ID) 152 } 153 154 n, err := s.store.Update(ctx, 155 []task.Task{old}, 156 WithTaskIDCond(EQ, old.ID), 157 WithTaskEpochCond(EQ, old.Epoch-1)) 158 if err != nil { 159 return err 160 } 161 if n == 0 { 162 return moerr.NewInvalidTask(ctx, taskRunner, value.ID) 163 } 164 return nil 165 } 166 167 func (s *taskService) Complete( 168 ctx context.Context, 169 taskRunner string, 170 value task.Task, 171 result task.ExecuteResult) error { 172 value.CompletedAt = time.Now().UnixMilli() 173 value.Status = task.TaskStatus_Completed 174 value.ExecuteResult = &result 175 n, err := s.store.Update(ctx, []task.Task{value}, 176 WithTaskStatusCond(EQ, task.TaskStatus_Running), 177 WithTaskRunnerCond(EQ, taskRunner), 178 WithTaskEpochCond(EQ, value.Epoch)) 179 if err != nil { 180 return err 181 } 182 if n == 0 { 183 return moerr.NewInvalidTask(ctx, value.TaskRunner, value.ID) 184 } 185 return nil 186 } 187 188 func (s *taskService) Heartbeat(ctx context.Context, value task.Task) error { 189 value.LastHeartbeat = time.Now().UnixMilli() 190 n, err := s.store.Update(ctx, []task.Task{value}, 191 WithTaskStatusCond(EQ, task.TaskStatus_Running), 192 WithTaskEpochCond(LE, value.Epoch), 193 WithTaskRunnerCond(EQ, value.TaskRunner)) 194 if err != nil { 195 return err 196 } 197 if n == 0 { 198 return moerr.NewInvalidTask(ctx, value.TaskRunner, value.ID) 199 } 200 return nil 201 } 202 203 func (s *taskService) QueryTask(ctx context.Context, conds ...Condition) ([]task.Task, error) { 204 return s.store.Query(ctx, conds...) 205 } 206 207 func (s *taskService) QueryCronTask(ctx context.Context) ([]task.CronTask, error) { 208 return s.store.QueryCronTask(ctx) 209 } 210 211 func (s *taskService) Close() error { 212 s.StopScheduleCronTask() 213 return s.store.Close() 214 } 215 216 func (s *taskService) GetStorage() TaskStorage { 217 return s.store 218 }