github.com/matrixorigin/matrixone@v1.2.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  	"errors"
    20  	"fmt"
    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/pb/task"
    26  	"github.com/robfig/cron/v3"
    27  )
    28  
    29  type taskService struct {
    30  	store      TaskStorage
    31  	cronParser cron.Parser
    32  	rt         runtime.Runtime
    33  
    34  	crons crons
    35  }
    36  
    37  // NewTaskService create a task service based on a task storage.
    38  func NewTaskService(
    39  	rt runtime.Runtime,
    40  	store TaskStorage) TaskService {
    41  	return &taskService{
    42  		rt:    rt,
    43  		store: store,
    44  		cronParser: cron.NewParser(
    45  			cron.Second |
    46  				cron.Minute |
    47  				cron.Hour |
    48  				cron.Dom |
    49  				cron.Month |
    50  				cron.Dow |
    51  				cron.Descriptor),
    52  	}
    53  }
    54  
    55  func (s *taskService) CreateAsyncTask(ctx context.Context, value task.TaskMetadata) error {
    56  	for {
    57  		select {
    58  		case <-ctx.Done():
    59  			return errors.Join(ctx.Err(), ErrNotReady)
    60  		default:
    61  			if _, err := s.store.AddAsyncTask(ctx, newTaskFromMetadata(value)); err != nil {
    62  				if errors.Is(err, ErrNotReady) {
    63  					time.Sleep(300 * time.Millisecond)
    64  					continue
    65  				}
    66  				return err
    67  			}
    68  			return nil
    69  		}
    70  	}
    71  }
    72  
    73  func (s *taskService) CreateBatch(ctx context.Context, tasks []task.TaskMetadata) error {
    74  	values := make([]task.AsyncTask, 0, len(tasks))
    75  	for _, v := range tasks {
    76  		values = append(values, newTaskFromMetadata(v))
    77  	}
    78  
    79  	for {
    80  		select {
    81  		case <-ctx.Done():
    82  			return errors.Join(ctx.Err(), ErrNotReady)
    83  		default:
    84  			if _, err := s.store.AddAsyncTask(ctx, values...); err != nil {
    85  				if errors.Is(err, ErrNotReady) {
    86  					time.Sleep(300 * time.Millisecond)
    87  					continue
    88  				}
    89  				return err
    90  			}
    91  			return nil
    92  		}
    93  	}
    94  }
    95  
    96  func (s *taskService) CreateCronTask(ctx context.Context, value task.TaskMetadata, cronExpr string) error {
    97  	sche, err := s.cronParser.Parse(cronExpr)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	now := time.Now().UnixMilli()
   103  	next := sche.Next(time.UnixMilli(now))
   104  
   105  	_, err = s.store.AddCronTask(ctx, task.CronTask{
   106  		Metadata:     value,
   107  		CronExpr:     cronExpr,
   108  		NextTime:     next.UnixMilli(),
   109  		TriggerTimes: 0,
   110  		CreateAt:     now,
   111  		UpdateAt:     now,
   112  	})
   113  	return err
   114  }
   115  
   116  func (s *taskService) CreateDaemonTask(ctx context.Context, metadata task.TaskMetadata, details *task.Details) error {
   117  	now := time.Now()
   118  
   119  	dt := task.DaemonTask{
   120  		Metadata:   metadata,
   121  		TaskType:   details.Type(),
   122  		TaskStatus: task.TaskStatus_Created,
   123  		Details:    details,
   124  		CreateAt:   now,
   125  		UpdateAt:   now,
   126  	}
   127  	_, err := s.store.AddDaemonTask(ctx, dt)
   128  	return err
   129  }
   130  
   131  func (s *taskService) Allocate(ctx context.Context, value task.AsyncTask, taskRunner string) error {
   132  	exists, err := s.store.QueryAsyncTask(ctx, WithTaskIDCond(EQ, value.ID))
   133  	if err != nil {
   134  		return err
   135  	}
   136  	if len(exists) != 1 {
   137  		s.rt.Logger().Debug(fmt.Sprintf("queried tasks: %v", exists))
   138  		s.rt.Logger().Fatal(fmt.Sprintf("query task by primary key, return %d records", len(exists)))
   139  	}
   140  
   141  	old := exists[0]
   142  	switch old.Status {
   143  	case task.TaskStatus_Running:
   144  		old.Epoch++
   145  		old.TaskRunner = taskRunner
   146  		old.LastHeartbeat = time.Now().UnixMilli()
   147  	case task.TaskStatus_Created:
   148  		old.Status = task.TaskStatus_Running
   149  		old.Epoch = 1
   150  		old.TaskRunner = taskRunner
   151  		old.LastHeartbeat = time.Now().UnixMilli()
   152  	default:
   153  		return moerr.NewInvalidTask(ctx, taskRunner, value.ID)
   154  	}
   155  
   156  	n, err := s.store.UpdateAsyncTask(ctx,
   157  		[]task.AsyncTask{old},
   158  		WithTaskIDCond(EQ, old.ID),
   159  		WithTaskEpochCond(EQ, old.Epoch-1))
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if n == 0 {
   164  		return moerr.NewInvalidTask(ctx, taskRunner, value.ID)
   165  	}
   166  	return nil
   167  }
   168  
   169  func (s *taskService) Complete(
   170  	ctx context.Context,
   171  	taskRunner string,
   172  	value task.AsyncTask,
   173  	result task.ExecuteResult) error {
   174  	value.CompletedAt = time.Now().UnixMilli()
   175  	value.Status = task.TaskStatus_Completed
   176  	value.ExecuteResult = &result
   177  	n, err := s.store.UpdateAsyncTask(ctx, []task.AsyncTask{value},
   178  		WithTaskStatusCond(task.TaskStatus_Running),
   179  		WithTaskRunnerCond(EQ, taskRunner),
   180  		WithTaskEpochCond(EQ, value.Epoch))
   181  	if err != nil {
   182  		return err
   183  	}
   184  	if n == 0 {
   185  		return moerr.NewInvalidTask(ctx, value.TaskRunner, value.ID)
   186  	}
   187  	return nil
   188  }
   189  
   190  func (s *taskService) Heartbeat(ctx context.Context, value task.AsyncTask) error {
   191  	value.LastHeartbeat = time.Now().UnixMilli()
   192  	n, err := s.store.UpdateAsyncTask(ctx, []task.AsyncTask{value},
   193  		WithTaskIDCond(EQ, value.ID),
   194  		WithTaskStatusCond(task.TaskStatus_Running),
   195  		WithTaskEpochCond(LE, value.Epoch),
   196  		WithTaskRunnerCond(EQ, value.TaskRunner))
   197  	if err != nil {
   198  		return err
   199  	}
   200  	if n == 0 {
   201  		return moerr.NewInvalidTask(ctx, value.TaskRunner, value.ID)
   202  	}
   203  	return nil
   204  }
   205  
   206  func (s *taskService) QueryAsyncTask(ctx context.Context, conds ...Condition) ([]task.AsyncTask, error) {
   207  	return s.store.QueryAsyncTask(ctx, conds...)
   208  }
   209  
   210  func (s *taskService) QueryCronTask(ctx context.Context, c ...Condition) ([]task.CronTask, error) {
   211  	return s.store.QueryCronTask(ctx, c...)
   212  }
   213  
   214  func (s *taskService) UpdateDaemonTask(ctx context.Context, tasks []task.DaemonTask, conds ...Condition) (int, error) {
   215  	return s.store.UpdateDaemonTask(ctx, tasks, conds...)
   216  }
   217  
   218  func (s *taskService) QueryDaemonTask(ctx context.Context, conds ...Condition) ([]task.DaemonTask, error) {
   219  	return s.store.QueryDaemonTask(ctx, conds...)
   220  }
   221  
   222  func (s *taskService) Close() error {
   223  	s.StopScheduleCronTask()
   224  	return s.store.Close()
   225  }
   226  
   227  func (s *taskService) GetStorage() TaskStorage {
   228  	return s.store
   229  }
   230  
   231  func (s *taskService) HeartbeatDaemonTask(ctx context.Context, t task.DaemonTask) error {
   232  	t.LastHeartbeat = time.Now().UTC()
   233  	n, err := s.store.HeartbeatDaemonTask(ctx, []task.DaemonTask{t})
   234  	if err != nil {
   235  		return err
   236  	}
   237  	if n == 0 {
   238  		return moerr.NewInvalidTask(ctx, t.TaskRunner, t.ID)
   239  	}
   240  	return nil
   241  }