github.com/matrixorigin/matrixone@v1.2.0/pkg/taskservice/task_service_cron_test.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  	"testing"
    20  	"time"
    21  
    22  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    23  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    24  	"github.com/matrixorigin/matrixone/pkg/common/stopper"
    25  	"github.com/matrixorigin/matrixone/pkg/pb/task"
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func TestScheduleCronTask(t *testing.T) {
    31  	runScheduleCronTaskTest(t, func(store *memTaskStorage, s *taskService, ctx context.Context) {
    32  		assert.NoError(t, s.CreateCronTask(ctx, newTestTaskMetadata("t1"), "*/1 * * * * *"))
    33  
    34  		s.StartScheduleCronTask()
    35  		defer s.StopScheduleCronTask()
    36  
    37  		waitHasTasks(t, store, time.Second*20, WithTaskParentTaskIDCond(EQ, "t1"))
    38  	})
    39  }
    40  
    41  func TestRetryScheduleCronTask(t *testing.T) {
    42  	runScheduleCronTaskTest(t, func(store *memTaskStorage, s *taskService, ctx context.Context) {
    43  		n := 0
    44  		store.preUpdateCron = func() error {
    45  			if n == 0 {
    46  				n++
    47  				return moerr.NewInfo(context.TODO(), "test error")
    48  			}
    49  			return nil
    50  		}
    51  
    52  		assert.NoError(t, s.CreateCronTask(ctx, newTestTaskMetadata("t1"), "*/1 * * * * *"))
    53  
    54  		s.StartScheduleCronTask()
    55  		defer s.StopScheduleCronTask()
    56  
    57  		waitHasTasks(t, store, time.Second*20, WithTaskParentTaskIDCond(EQ, "t1"))
    58  	})
    59  }
    60  
    61  func TestScheduleCronTaskImmediately(t *testing.T) {
    62  	runScheduleCronTaskTest(t, func(store *memTaskStorage, s *taskService, ctx context.Context) {
    63  		task := newTestCronTask("t1", "*/1 * * * * *")
    64  		task.CreateAt = time.Now().UnixMilli()
    65  		task.NextTime = task.CreateAt
    66  		task.TriggerTimes = 0
    67  		task.UpdateAt = time.Now().UnixMilli()
    68  
    69  		mustAddTestCronTask(t, store, 1, task)
    70  
    71  		s.StartScheduleCronTask()
    72  		defer s.StopScheduleCronTask()
    73  
    74  		waitHasTasks(t, store, time.Second*20, WithTaskParentTaskIDCond(EQ, "t1"))
    75  	})
    76  }
    77  
    78  func TestScheduleCronTaskLimitConcurrency(t *testing.T) {
    79  	runScheduleCronTaskTest(t, func(store *memTaskStorage, s *taskService, ctx context.Context) {
    80  		cronTask := newTestCronTask("t1", "* * * * ? *")
    81  		cronTask.CreateAt = time.Now().UnixMilli()
    82  		cronTask.NextTime = cronTask.CreateAt
    83  		cronTask.TriggerTimes = 0
    84  		cronTask.UpdateAt = time.Now().UnixMilli()
    85  		cronTask.Metadata.Options.Concurrency = 1
    86  
    87  		mustAddTestCronTask(t, store, 1, cronTask)
    88  
    89  		s.StartScheduleCronTask()
    90  		defer s.StopScheduleCronTask()
    91  
    92  		waitHasTasks(t, store, time.Second*20,
    93  			WithTaskParentTaskIDCond(EQ, "t1"))
    94  		assertTaskCountEqual(t, store, time.Second*5, 1,
    95  			WithTaskParentTaskIDCond(EQ, "t1"),
    96  			WithTaskStatusCond(task.TaskStatus_Running))
    97  	})
    98  }
    99  
   100  func TestRemovedCronTask(t *testing.T) {
   101  	runScheduleCronTaskTest(t, func(store *memTaskStorage, s *taskService, ctx context.Context) {
   102  		assert.NoError(t, s.CreateCronTask(ctx, newTestTaskMetadata("t1"), "*/1 * * * * *"))
   103  
   104  		s.StartScheduleCronTask()
   105  		defer s.StopScheduleCronTask()
   106  		time.Sleep(time.Second * 3)
   107  		s.crons.stopForTest()
   108  		waitJobsCount(t, 1, s, time.Second*10)
   109  
   110  		store.Lock()
   111  		store.cronTaskIndexes = make(map[string]uint64)
   112  		store.cronTasks = make(map[uint64]task.CronTask)
   113  		store.Unlock()
   114  
   115  		s.crons.startForTest(t, s.fetchCronTasks)
   116  		time.Sleep(time.Second * 3)
   117  		s.crons.stopForTest()
   118  		waitJobsCount(t, 0, s, time.Second*10)
   119  	})
   120  }
   121  
   122  func TestReplaceCronTask(t *testing.T) {
   123  	runScheduleCronTaskTest(t, func(store *memTaskStorage, s *taskService, ctx context.Context) {
   124  		assert.NoError(t, s.CreateCronTask(ctx, newTestTaskMetadata("t1"), "*/1 * * * * *"))
   125  		s.StartScheduleCronTask()
   126  		defer s.StopScheduleCronTask()
   127  		time.Sleep(time.Second * 3)
   128  		s.crons.stopForTest()
   129  
   130  		jobInCron := s.crons.jobs[1]
   131  		taskInStore := store.cronTasks[jobInCron.task.ID]
   132  
   133  		require.Equal(t, jobInCron.task.TriggerTimes, taskInStore.TriggerTimes)
   134  
   135  		t.Log("set trigger times to 0")
   136  		jobInCron.task.TriggerTimes = 0
   137  		require.Equal(t, s.crons.jobs[1].task.TriggerTimes, uint64(0))
   138  
   139  		s.crons.startForTest(t, s.fetchCronTasks)
   140  		time.Sleep(time.Second * 3)
   141  		s.crons.stopForTest()
   142  
   143  		jobInCron = s.crons.jobs[1]
   144  		taskInStore = store.cronTasks[jobInCron.task.ID]
   145  		require.Equal(t, jobInCron.task.TriggerTimes, taskInStore.TriggerTimes)
   146  	})
   147  }
   148  
   149  func (c *crons) stopForTest() {
   150  	c.stopper.Stop()
   151  	<-c.cron.Stop().Done()
   152  }
   153  
   154  func (c *crons) startForTest(t *testing.T, fn func(ctx context.Context)) {
   155  	c.cron.Start()
   156  	c.stopper = stopper.NewStopper("cronTasks")
   157  	require.NoError(t, c.stopper.RunTask(fn))
   158  }
   159  
   160  func runScheduleCronTaskTest(t *testing.T, testFunc func(*memTaskStorage, *taskService, context.Context)) {
   161  	fetchInterval = 300 * time.Millisecond
   162  
   163  	store := NewMemTaskStorage().(*memTaskStorage)
   164  	s := NewTaskService(runtime.DefaultRuntime(), store).(*taskService)
   165  	defer func() {
   166  		assert.NoError(t, s.Close())
   167  	}()
   168  
   169  	ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)
   170  	defer cancel()
   171  	testFunc(store, s, ctx)
   172  }
   173  
   174  func waitJobsCount(t *testing.T, n int, s *taskService, timeout time.Duration) {
   175  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   176  	defer cancel()
   177  
   178  	defer func() {
   179  		require.Equal(t, n, len(s.crons.entries))
   180  	}()
   181  
   182  	for {
   183  		select {
   184  		case <-ctx.Done():
   185  			return
   186  		default:
   187  			if len(s.crons.entries) == n {
   188  				return
   189  			}
   190  		}
   191  		time.Sleep(time.Millisecond * 10)
   192  	}
   193  }
   194  
   195  func waitHasTasks(t *testing.T, store *memTaskStorage, timeout time.Duration, conds ...Condition) {
   196  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   197  	defer cancel()
   198  
   199  	for {
   200  		select {
   201  		case <-ctx.Done():
   202  			require.Fail(t, "wait any tasks failed")
   203  			return
   204  		default:
   205  			tasks, err := store.QueryAsyncTask(ctx, conds...)
   206  			require.NoError(t, err)
   207  			if len(tasks) > 0 {
   208  				return
   209  			}
   210  		}
   211  		time.Sleep(time.Millisecond * 10)
   212  	}
   213  }
   214  
   215  func assertTaskCountEqual(t *testing.T, store *memTaskStorage, timeout time.Duration, count int, conds ...Condition) {
   216  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   217  	defer cancel()
   218  
   219  	for {
   220  		select {
   221  		case <-ctx.Done():
   222  			return
   223  		default:
   224  			tasks, err := store.QueryAsyncTask(ctx, conds...)
   225  			require.NoError(t, err)
   226  			require.LessOrEqual(t, len(tasks), count, "err")
   227  		}
   228  		time.Sleep(time.Second)
   229  	}
   230  }