github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/async/async_initializer.go (about) 1 // Copyright 2024 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package async 15 16 import ( 17 "context" 18 "sync" 19 20 "github.com/pingcap/log" 21 "github.com/pingcap/tiflow/pkg/errors" 22 "github.com/pingcap/tiflow/pkg/workerpool" 23 "go.uber.org/atomic" 24 "go.uber.org/zap" 25 ) 26 27 // Initializer is a helper struct to initialize a changefeed asynchronously. 28 type Initializer struct { 29 // state related fields 30 initialized *atomic.Bool 31 initializing *atomic.Bool 32 initError *atomic.Error 33 34 // func to cancel the changefeed initialization 35 cancelInitialize context.CancelFunc 36 initialWaitGroup sync.WaitGroup 37 } 38 39 // NewInitializer creates a new initializer. 40 func NewInitializer() *Initializer { 41 return &Initializer{ 42 initialized: atomic.NewBool(false), 43 initializing: atomic.NewBool(false), 44 initError: atomic.NewError(nil), 45 } 46 } 47 48 // TryInitialize tries to initialize the module asynchronously. 49 // returns true if the module is already initialized or initialized successfully. 50 // returns false if the module is initializing or failed to initialize. 51 // returns error if the module failed to initialize. 52 // It will only initialize the module once. 53 // It's not thread-safe. 54 func (initializer *Initializer) TryInitialize(ctx context.Context, 55 initFunc func(ctx context.Context) error, 56 pool workerpool.AsyncPool, 57 ) (bool, error) { 58 if initializer.initialized.Load() { 59 return true, nil 60 } 61 if initializer.initializing.CompareAndSwap(false, true) { 62 initialCtx, cancelInitialize := context.WithCancel(ctx) 63 initializer.initialWaitGroup.Add(1) 64 initializer.cancelInitialize = cancelInitialize 65 log.Info("submit async initializer task to the worker pool") 66 err := pool.Go(initialCtx, func() { 67 defer initializer.initialWaitGroup.Done() 68 if err := initFunc(initialCtx); err != nil { 69 initializer.initError.Store(errors.Trace(err)) 70 } else { 71 initializer.initialized.Store(true) 72 } 73 }) 74 if err != nil { 75 log.Error("failed to submit async initializer task to the worker pool", zap.Error(err)) 76 initializer.initialWaitGroup.Done() 77 return false, errors.Trace(err) 78 } 79 } 80 if initializer.initError.Load() != nil { 81 return false, errors.Trace(initializer.initError.Load()) 82 } 83 return initializer.initialized.Load(), nil 84 } 85 86 // Terminate terminates the initializer. 87 // It will cancel the initialization if it is initializing. and wait for the initialization to finish. 88 // It's not thread-safe. 89 func (initializer *Initializer) Terminate() { 90 if initializer.initializing.Load() { 91 if initializer.cancelInitialize != nil { 92 initializer.cancelInitialize() 93 } 94 initializer.initialWaitGroup.Wait() 95 } 96 initializer.initializing.Store(false) 97 initializer.initialized.Store(false) 98 initializer.initError.Store(nil) 99 }