github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/lightning/common/pause.go (about) 1 // Copyright 2019 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 common 15 16 import ( 17 "context" 18 "runtime" 19 "sync/atomic" 20 ) 21 22 const ( 23 // pauseStateRunning indicates the pauser is running (not paused) 24 pauseStateRunning uint32 = iota 25 // pauseStatePaused indicates the pauser is paused 26 pauseStatePaused 27 // pauseStateLocked indicates the pauser is being held for exclusive access 28 // of its waiters field, and no other goroutines should be able to 29 // read/write the map of waiters when the state is Locked (all other 30 // goroutines trying to access waiters should cooperatively enter a spin 31 // loop). 32 pauseStateLocked 33 ) 34 35 // The implementation is based on https://github.com/golang/sync/blob/master/semaphore/semaphore.go 36 37 // Pauser is a type which could allow multiple goroutines to wait on demand, 38 // similar to a gate or traffic light. 39 type Pauser struct { 40 // state has two purposes: (1) records whether we are paused, and (2) acts 41 // as a spin-lock for the `waiters` map. 42 state uint32 43 waiters map[chan<- struct{}]struct{} 44 } 45 46 // NewPauser returns an initialized pauser. 47 func NewPauser() *Pauser { 48 return &Pauser{ 49 state: pauseStateRunning, 50 waiters: make(map[chan<- struct{}]struct{}, 32), 51 } 52 } 53 54 // Pause causes all calls to Wait() to block. 55 func (p *Pauser) Pause() { 56 // If the state was Paused, we do nothing. 57 // If the state was Locked, we loop again until the state becomes not Locked. 58 // If the state was Running, we atomically move into Paused state. 59 60 for { 61 oldState := atomic.LoadUint32(&p.state) 62 if oldState == pauseStatePaused || atomic.CompareAndSwapUint32(&p.state, pauseStateRunning, pauseStatePaused) { 63 return 64 } 65 runtime.Gosched() 66 } 67 } 68 69 // Resume causes all calls to Wait() to continue. 70 func (p *Pauser) Resume() { 71 // If the state was Running, we do nothing. 72 // If the state was Locked, we loop again until the state becomes not Locked. 73 // If the state was Paused, we Lock the pauser, clear the waiter map, 74 // then move into Running state. 75 76 for { 77 oldState := atomic.LoadUint32(&p.state) 78 if oldState == pauseStateRunning { 79 return 80 } 81 if atomic.CompareAndSwapUint32(&p.state, pauseStatePaused, pauseStateLocked) { 82 break 83 } 84 runtime.Gosched() 85 } 86 87 // extract all waiters, then notify them we changed from "Paused" to "Not Paused". 88 allWaiters := p.waiters 89 p.waiters = make(map[chan<- struct{}]struct{}, len(allWaiters)) 90 91 atomic.StoreUint32(&p.state, pauseStateRunning) 92 93 for waiter := range allWaiters { 94 close(waiter) 95 } 96 } 97 98 // IsPaused gets whether the current state is paused or not. 99 func (p *Pauser) IsPaused() bool { 100 return atomic.LoadUint32(&p.state) != pauseStateRunning 101 } 102 103 // Wait blocks the current goroutine if the current state is paused, until the 104 // pauser itself is resumed at least once. 105 // 106 // If `ctx` is done, this method will also unblock immediately, and return the 107 // context error. 108 func (p *Pauser) Wait(ctx context.Context) error { 109 // If the state is Running, we return immediately (this path is hot and must 110 // be taken as soon as possible) 111 // If the state is Locked, we loop again until the state becomes not Locked. 112 // If the state is Paused, we Lock the pauser, add a waiter to the map, then 113 // revert to the original (Paused) state. 114 115 for { 116 oldState := atomic.LoadUint32(&p.state) 117 if oldState == pauseStateRunning { 118 return nil 119 } 120 if atomic.CompareAndSwapUint32(&p.state, pauseStatePaused, pauseStateLocked) { 121 break 122 } 123 runtime.Gosched() 124 } 125 126 waiter := make(chan struct{}) 127 p.waiters[waiter] = struct{}{} 128 129 atomic.StoreUint32(&p.state, pauseStatePaused) 130 131 select { 132 case <-ctx.Done(): 133 err := ctx.Err() 134 p.cancel(waiter) 135 return err 136 case <-waiter: 137 return nil 138 } 139 } 140 141 // cancel removes a waiter from the waiters map 142 func (p *Pauser) cancel(waiter chan<- struct{}) { 143 // If the state is Locked, we loop again until the state becomes not Locked. 144 // Otherwise, we Lock the pauser, remove the waiter from the map, then 145 // revert to the original state. 146 147 for { 148 oldState := atomic.LoadUint32(&p.state) 149 if oldState != pauseStateLocked && atomic.CompareAndSwapUint32(&p.state, oldState, pauseStateLocked) { 150 delete(p.waiters, waiter) 151 atomic.StoreUint32(&p.state, oldState) 152 return 153 } 154 runtime.Gosched() 155 } 156 }