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 }