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

     1  package cpusharer
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"runtime"
     7  	"time"
     8  
     9  	"github.com/Cloud-Foundations/Dominator/lib/format"
    10  )
    11  
    12  func newFifoCpuSharer() *FifoCpuSharer {
    13  	return &FifoCpuSharer{
    14  		semaphore:        make(chan struct{}, runtime.NumCPU()),
    15  		grabTimeout:      -1,
    16  		lastAcquireEvent: time.Now(),
    17  		lastIdleEvent:    time.Now(),
    18  		lastYieldEvent:   time.Now(),
    19  	}
    20  }
    21  
    22  func (s *FifoCpuSharer) getStatistics() Statistics {
    23  	s.mutex.Lock()
    24  	defer s.mutex.Unlock()
    25  	s.Statistics.LastAcquireEvent = s.lastAcquireEvent
    26  	s.Statistics.LastIdleEvent = s.lastIdleEvent
    27  	s.Statistics.LastYieldEvent = s.lastYieldEvent
    28  	s.Statistics.NumCpuRunning = uint(len(s.semaphore))
    29  	s.Statistics.NumCpu = uint(cap(s.semaphore))
    30  	s.Statistics.NumIdleEvents = s.numIdleEvents
    31  	return s.Statistics
    32  }
    33  
    34  func (s *FifoCpuSharer) goWhenIdle(minIdleTime, timeout time.Duration,
    35  	goFunc func()) bool {
    36  	if !s.grabIdleCpu(minIdleTime, timeout) {
    37  		return false
    38  	}
    39  	go func() {
    40  		goFunc()
    41  		s.ReleaseCpu()
    42  	}()
    43  	return true
    44  }
    45  
    46  func (s *FifoCpuSharer) grabCpu() {
    47  	select {
    48  	case s.semaphore <- struct{}{}: // A CPU is immediately available.
    49  		s.mutex.Lock()
    50  		defer s.mutex.Unlock()
    51  		s.lastAcquireEvent = time.Now()
    52  		s.lastIdleEvent = s.lastAcquireEvent
    53  		s.numIdleEvents++
    54  		return
    55  	default: // No CPU is available yet: block waiting with timeout.
    56  		s.mutex.Lock()
    57  		timeout := s.grabTimeout
    58  		s.mutex.Unlock()
    59  		if timeout < 0 {
    60  			s.semaphore <- struct{}{} // Block forever waiting for a CPU.
    61  			s.mutex.Lock()
    62  			defer s.mutex.Unlock()
    63  			s.lastAcquireEvent = time.Now()
    64  			return
    65  		}
    66  		timer := time.NewTimer(timeout)
    67  		select {
    68  		case s.semaphore <- struct{}{}: // A CPU became available.
    69  			if !timer.Stop() {
    70  				<-timer.C
    71  			}
    72  			s.mutex.Lock()
    73  			defer s.mutex.Unlock()
    74  			s.lastAcquireEvent = time.Now()
    75  			return
    76  		case <-timer.C:
    77  			stats := s.GetStatistics()
    78  			fmt.Fprintf(os.Stderr,
    79  				"CPU grabber timeout. Last acquire: %s, last yield: %s\n",
    80  				format.Duration(time.Since(stats.LastAcquireEvent)),
    81  				format.Duration(time.Since(stats.LastYieldEvent)))
    82  			fmt.Fprintln(os.Stderr, "Full stack track follows:")
    83  			buf := make([]byte, 1024*1024)
    84  			nBytes := runtime.Stack(buf, true)
    85  			os.Stderr.Write(buf[0:nBytes])
    86  			os.Stderr.Write([]byte("\n"))
    87  			panic("timeout")
    88  		}
    89  	}
    90  }
    91  
    92  func (s *FifoCpuSharer) grabIdleCpu(minIdleTime, timeout time.Duration) bool {
    93  	var timeoutTime time.Time
    94  	if timeout >= 0 {
    95  		timeoutTime = time.Now().Add(timeout)
    96  	}
    97  	for timeout < 0 || time.Now().Before(timeoutTime) {
    98  		select {
    99  		case s.semaphore <- struct{}{}: // A CPU was idle.
   100  			if minIdleTime > 0 {
   101  				<-s.semaphore // Yield CPU and maybe check again later.
   102  				if timeout >= 0 {
   103  					if minIdleTime > time.Until(timeoutTime) {
   104  						return false
   105  					}
   106  				}
   107  				time.Sleep(minIdleTime)
   108  				select {
   109  				case s.semaphore <- struct{}{}: // A CPU was idle.
   110  				default: // No idle CPU: try again.
   111  					continue
   112  				}
   113  			}
   114  			s.mutex.Lock()
   115  			defer s.mutex.Unlock()
   116  			s.lastAcquireEvent = time.Now()
   117  			s.lastIdleEvent = s.lastAcquireEvent
   118  			s.numIdleEvents++
   119  			return true
   120  		default: // No CPU is available yet: wait with possible timeout.
   121  			if timeout < 0 {
   122  				s.semaphore <- struct{}{}
   123  			} else {
   124  				timer := time.NewTimer(time.Until(timeoutTime))
   125  				select {
   126  				case s.semaphore <- struct{}{}: // A CPU became available.
   127  					if !timer.Stop() {
   128  						<-timer.C
   129  					}
   130  				case <-timer.C:
   131  					return false // Timeout.
   132  				}
   133  			}
   134  			// Yield CPU and give someone else a chance to ensure really idle.
   135  			<-s.semaphore
   136  			runtime.Gosched()
   137  		}
   138  	}
   139  	return false
   140  }
   141  
   142  func (s *FifoCpuSharer) releaseCpu() {
   143  	s.mutex.Lock()
   144  	defer s.mutex.Unlock()
   145  	s.lastYieldEvent = time.Now()
   146  	<-s.semaphore
   147  }
   148  
   149  func (s *FifoCpuSharer) setGrabTimeout(timeout time.Duration) {
   150  	s.mutex.Lock()
   151  	defer s.mutex.Unlock()
   152  	s.grabTimeout = timeout
   153  }