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  }