github.com/matrixorigin/matrixone@v1.2.0/pkg/vm/engine/tae/gc/manager.go (about)

     1  // Copyright 2021 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 gc
    16  
    17  import (
    18  	"context"
    19  	"math"
    20  	"sort"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    25  	"github.com/matrixorigin/matrixone/pkg/common/stopper"
    26  	"github.com/matrixorigin/matrixone/pkg/logutil"
    27  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/logstore/sm"
    28  )
    29  
    30  // manager is initialized with some cron jobs
    31  // it doesn't support dynamically adding or removing jobs.
    32  type Manager struct {
    33  	// cron jobs
    34  	jobs cronJobs
    35  	// name index of cron jobs for dedup
    36  	nameIdx map[string]*cronJob
    37  
    38  	// main loop stopper
    39  	loopStopper *stopper.Stopper
    40  
    41  	// job process queue
    42  	processQueue sm.Queue
    43  
    44  	onceStart sync.Once
    45  	onceStop  sync.Once
    46  }
    47  
    48  func NewManager(options ...Option) *Manager {
    49  	mgr := &Manager{
    50  		nameIdx: make(map[string]*cronJob),
    51  	}
    52  	for _, opt := range options {
    53  		opt(mgr)
    54  	}
    55  	mgr.loopStopper = stopper.NewStopper("gc-loop")
    56  	mgr.processQueue = sm.NewSafeQueue(10000, 20, mgr.process)
    57  	return mgr
    58  }
    59  
    60  func (mgr *Manager) addJob(
    61  	name string,
    62  	interval time.Duration,
    63  	job Job,
    64  ) {
    65  	if _, found := mgr.nameIdx[name]; found {
    66  		panic(moerr.NewInternalErrorNoCtx("duplicate gc job: %s", name))
    67  	}
    68  	cj := &cronJob{
    69  		name:     name,
    70  		interval: interval,
    71  		job:      job,
    72  	}
    73  	mgr.nameIdx[name] = cj
    74  	mgr.jobs = append(mgr.jobs, cj)
    75  }
    76  
    77  func (mgr *Manager) process(jobs ...any) {
    78  	jobSet := make(map[string]bool)
    79  	var dedupJobs []*cronJob
    80  	for _, job := range jobs {
    81  		cj := job.(*cronJob)
    82  		if _, found := jobSet[cj.name]; found {
    83  			continue
    84  		} else {
    85  			jobSet[cj.name] = true
    86  			dedupJobs = append(dedupJobs, cj)
    87  		}
    88  	}
    89  	if len(jobSet) == 0 {
    90  		return
    91  	}
    92  	for _, cj := range dedupJobs {
    93  		logutil.Debugf("processing %s", cj.String())
    94  		if err := cj.job(context.Background()); err != nil {
    95  			logutil.Errorf("process gc job %s: %v", cj.name, err)
    96  		}
    97  	}
    98  }
    99  
   100  // main run loop
   101  //  1. init all gc cron jobs
   102  //  2. loop
   103  //     2.1 sort all cron jobs by next time
   104  //     2.2 create a timer using the jobs' minimum next time
   105  //     2.3
   106  //     2.3.1 wait timer timeout. enqueue jobs with the next time before the
   107  //     timer's timeout time into the process queue. reschdule the job
   108  //     2.3.2 wait context timeout. exit the loop
   109  func (mgr *Manager) loop(ctx context.Context) {
   110  	// init all job next time
   111  	now := time.Now()
   112  	for _, job := range mgr.jobs {
   113  		job.init(now)
   114  	}
   115  
   116  	// use the minimum job interval as the timer duration
   117  	var timer *time.Timer
   118  
   119  	resetTimer := func() {
   120  		if mgr.jobs.Len() == 0 {
   121  			timer = time.NewTimer(time.Second * time.Duration(math.MaxInt32))
   122  		} else {
   123  			dur := mgr.jobs[0].next.Sub(now)
   124  			timer = time.NewTimer(dur)
   125  		}
   126  	}
   127  
   128  	for {
   129  		// sort all jobs by next time
   130  		sort.Sort(mgr.jobs)
   131  
   132  		// reset timer
   133  		resetTimer()
   134  
   135  		select {
   136  		case <-timer.C:
   137  			now = time.Now()
   138  			for _, job := range mgr.jobs {
   139  				// if job next time is after now, skip this run
   140  				if job.after(now) {
   141  					break
   142  				}
   143  				if _, err := mgr.processQueue.Enqueue(job); err != nil {
   144  					logutil.Errorf("enqueue gc job %s: %s", job.name, err)
   145  					break
   146  				}
   147  				job.reschedule(now)
   148  			}
   149  
   150  		// stop this loop
   151  		case <-ctx.Done():
   152  			logutil.Info("gc-loop is going to exit")
   153  			return
   154  		}
   155  	}
   156  }
   157  
   158  func (mgr *Manager) Start() {
   159  	mgr.onceStart.Do(func() {
   160  		mgr.processQueue.Start()
   161  		if err := mgr.loopStopper.RunNamedTask(
   162  			"run-gc-loop",
   163  			mgr.loop,
   164  		); err != nil {
   165  			panic(err)
   166  		}
   167  	})
   168  }
   169  
   170  func (mgr *Manager) Stop() {
   171  	mgr.onceStop.Do(func() {
   172  		mgr.loopStopper.Stop()
   173  		mgr.processQueue.Stop()
   174  	})
   175  }