github.com/pingcap/tidb-lightning@v5.0.0-rc.0.20210428090220-84b649866577+incompatible/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  }