github.com/Cloud-Foundations/Dominator@v0.3.4/lib/cpusharer/api.go (about)

     1  /*
     2  	Package cpusharer implements co-operative CPU sharing between goroutines.
     3  
     4  	Package cpusharer may be used by groups of co-operating goroutines to share
     5  	CPU resources so that blocking operations are fully concurrent but avoiding
     6  	the thundering herd problem when large numbers of goroutines need the CPU,
     7  	impacting the responsiveness of other goroutines such as dashboards and
     8  	health checks.
     9  	Each goroutine calls the GrabCpu method when it starts and wraps blocking
    10  	operations with a pair of ReleaseCpu/GrabCpu calls.
    11  	A typical programming pattern is:
    12  		cpuSharer := cpusharer.New*CpuSharer() // Pick your sharer of choice.
    13  		for work := range workChannel {
    14  			cpuSharer.GoWhenIdle(0, -1, func(work workType) {
    15  				work.compute()
    16  				cpuSharer.ReleaseCpu()
    17  				work.block()
    18  				cpuSharer.GrabCpu()
    19  				work.moreCompute()
    20  			}(work)
    21  		}
    22  */
    23  package cpusharer
    24  
    25  import (
    26  	"sync"
    27  	"time"
    28  )
    29  
    30  // CpuSharer is the interface that wraps the GrabCpu and ReleaseCpu methods.
    31  //
    32  // GrabCpu will grab a CPU for use. If there are none available (i.e. all CPUs
    33  // are in use by other co-operating goroutines) then this will block until a CPU
    34  // is available.
    35  //
    36  // ReleaseCpu will release a CPU so that another co-operating goroutine can grab
    37  // a CPU.
    38  type CpuSharer interface {
    39  	GrabCpu()
    40  	ReleaseCpu()
    41  }
    42  
    43  type FifoCpuSharer struct {
    44  	semaphore        chan struct{}
    45  	mutex            sync.Mutex
    46  	grabTimeout      time.Duration
    47  	lastAcquireEvent time.Time
    48  	lastIdleEvent    time.Time
    49  	lastYieldEvent   time.Time
    50  	numIdleEvents    uint64
    51  	Statistics       Statistics
    52  }
    53  
    54  // NewFifoCpuSharer creates a simple FIFO CpuSharer. CPU access is granted in
    55  // the order in which they are requested.
    56  func NewFifoCpuSharer() *FifoCpuSharer {
    57  	return newFifoCpuSharer()
    58  }
    59  
    60  // GetStatistics will update and return the Statistics.
    61  func (s *FifoCpuSharer) GetStatistics() Statistics {
    62  	return s.getStatistics()
    63  }
    64  
    65  // SetGrabTimeout will change the timeout for the GrabCpu method. A negative
    66  // value for timeout means no timeout (this is the default). After the timeout a
    67  // panic is generated.
    68  // A full stack trace is written to os.Stderr.
    69  func (s *FifoCpuSharer) SetGrabTimeout(timeout time.Duration) {
    70  	s.setGrabTimeout(timeout)
    71  }
    72  
    73  // Go will start a goroutine and return. The goroutine will grab a CPU using the
    74  // GrabCpu method, then will run goFunc. When goFunc returns the CPU is
    75  // released.
    76  func (s *FifoCpuSharer) Go(goFunc func()) {
    77  	startGoroutine(s, goFunc)
    78  }
    79  
    80  // GoWhenAvailable will grab a CPU using the GrabCpu method and then starts a
    81  // goroutine which will run goFunc. When goFunc returns the CPU is released.
    82  // Use GoWhenAvailable to limit the addition of more goroutines if the CPUs are
    83  // already saturated with work. This can reduce memory consumption spikes.
    84  func (s *FifoCpuSharer) GoWhenAvailable(goFunc func()) {
    85  	startGoroutineWhenAvailable(s, goFunc)
    86  }
    87  
    88  // GoWhenIdle is similar to the GoWhenAvailable method except that it will call
    89  // GrabIdleCpu to wait for and grab an idle CPU. Compared to GoWhenAvailable,
    90  // GoWhenIdle effectively lowers the priority of starting new goroutines below
    91  // the priority of the co-operating goroutines. This can be even more effective
    92  // in reducing memory consumption spikes.
    93  // GoWhenIdle will wait up to timeout (if negative, it will wait forever) for an
    94  // idle CPU. If an idle CPU is grabbed before the timeout it will return true,
    95  // otherwise it will not start a goroutine and will return false.
    96  func (s *FifoCpuSharer) GoWhenIdle(minIdleTime, timeout time.Duration,
    97  	goFunc func()) bool {
    98  	return s.goWhenIdle(minIdleTime, timeout, goFunc)
    99  }
   100  
   101  // GrabCpu will grab a CPU for use. If there are none available (i.e. all CPUs
   102  // are in use by other co-operating goroutines) then this will block until a CPU
   103  // is available. Grab requests are fulfilled in the order they are made.
   104  func (s *FifoCpuSharer) GrabCpu() {
   105  	s.grabCpu()
   106  }
   107  
   108  // GrabIdleCpu will wait for a CPU to be idle for at least minIdleTime and then
   109  // grabs a CPU. If minIdleTime is zero or less then a CPU is grabbed immediately
   110  // once one becomes idle. CPUs will never become idle while there are more
   111  // goroutines blocked on GrabCpu than there are CPUs.
   112  // GrabIdleCpu will wait up to timeout (if negative, it will wait forever) for
   113  // an idle CPU. If an idle CPU is grabbed before the timeout it will return
   114  // true, otherwise it will return false.
   115  func (s *FifoCpuSharer) GrabIdleCpu(minIdleTime, timeout time.Duration) bool {
   116  	return s.grabIdleCpu(minIdleTime, timeout)
   117  }
   118  
   119  // GrabSemaphore will safely grab the provided semaphore, releasing and
   120  // re-acquiring the CPU if the semaphore blocks. Use this to avoid deadlocks.
   121  func (s *FifoCpuSharer) GrabSemaphore(semaphore chan<- struct{}) {
   122  	grabSemaphore(s, semaphore)
   123  }
   124  
   125  func (s *FifoCpuSharer) ReleaseCpu() {
   126  	s.releaseCpu()
   127  }
   128  
   129  func (s *FifoCpuSharer) Sleep(duration time.Duration) {
   130  	sleep(s, duration)
   131  }
   132  
   133  type Statistics struct {
   134  	LastAcquireEvent time.Time
   135  	LastIdleEvent    time.Time
   136  	LastYieldEvent   time.Time
   137  	NumCpuRunning    uint
   138  	NumCpu           uint
   139  	NumIdleEvents    uint64
   140  }