github.com/haraldrudell/parl@v0.4.176/halt/halt-detector.go (about)

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  // Package halt detects Go runtime execution halts.
     7  package halt
     8  
     9  import (
    10  	"time"
    11  
    12  	"github.com/haraldrudell/parl"
    13  )
    14  
    15  const (
    16  	// timeSleepDuration is how long sleep is requested from the Go runtime time.Sleep function
    17  	//	- needs to be about 10x shorter than the advertised minimum Go garbage-collector stop-the-world
    18  	//		intervale, which is 1 ms
    19  	timeSleepDuration         = 100 * time.Microsecond
    20  	defaultReportingThreshold = 500 * time.Microsecond
    21  )
    22  
    23  // HaltDetector sends detected Go runtime execution halts on channel ch.
    24  type HaltDetector struct {
    25  	reportingThreshold time.Duration
    26  	ch                 parl.NBChan[*HaltReport]
    27  }
    28  
    29  // HaltReport is a value object representing a detected Go runtime execution halt
    30  type HaltReport struct {
    31  	N int           // report number 1…
    32  	T time.Time     // when halt started
    33  	D time.Duration // halt duration
    34  }
    35  
    36  // NewHaltDetector returns an object that sends detected Go runtime execution halts on a channel
    37  func NewHaltDetector(reportingThreshold ...time.Duration) (haltDetector *HaltDetector) {
    38  	var t time.Duration
    39  	if len(reportingThreshold) > 0 {
    40  		t = reportingThreshold[0]
    41  	}
    42  	if t < defaultReportingThreshold {
    43  		t = defaultReportingThreshold
    44  	}
    45  	return &HaltDetector{reportingThreshold: t}
    46  }
    47  
    48  // Thread detects execution halts and sends them on h.ch
    49  func (h *HaltDetector) Thread(g0 parl.Go) {
    50  	var err error
    51  	defer g0.Register().Done(&err)
    52  	defer parl.PanicToErr(&err)
    53  
    54  	timeTicker := time.NewTicker(time.Millisecond)
    55  	defer timeTicker.Stop()
    56  
    57  	var done = g0.Context().Done()
    58  	var elapsed time.Duration
    59  	var t0 time.Time
    60  	var t1 = time.Now()
    61  	var reportingThreshold = h.reportingThreshold
    62  	var n int
    63  	var C = timeTicker.C
    64  	for {
    65  
    66  		// sleep
    67  		t0 = t1
    68  		select {
    69  		case <-done:
    70  			return // g0 context cancel return
    71  		case <-C:
    72  		}
    73  		t1 = time.Now()
    74  		elapsed = t1.Sub(t0)
    75  
    76  		// report
    77  		if elapsed >= reportingThreshold {
    78  			n++
    79  			h.ch.Send(&HaltReport{N: n, T: t0, D: elapsed})
    80  		}
    81  	}
    82  }
    83  
    84  // Ch returns a receive channel for reports of Go rutnime execution halts
    85  //   - Ch never closes
    86  func (h *HaltDetector) Ch() (ch <-chan *HaltReport) {
    87  	return h.ch.Ch()
    88  }