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

     1  /*
     2  © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package parl
     7  
     8  import (
     9  	"sync"
    10  	"sync/atomic"
    11  	"time"
    12  
    13  	"github.com/haraldrudell/parl/parli"
    14  	"github.com/haraldrudell/parl/perrors"
    15  	"github.com/haraldrudell/parl/pmaps"
    16  	"github.com/haraldrudell/parl/sets"
    17  )
    18  
    19  const (
    20  	SlowDefault slowType = iota
    21  	SlowOwnThread
    22  	SlowShutdownThread
    23  
    24  	slowScanPeriod = time.Second
    25  )
    26  
    27  // shared SlowDetectorThread for SlowDefault threads
    28  var slowDetectorThread SlowDetectorThread
    29  
    30  type SlowDetectorThread struct {
    31  	slowTyp         slowType
    32  	nonReturnPeriod time.Duration
    33  	slowMap         pmaps.RWMap[slowID, *SlowDetectorInvocation]
    34  	hasThread       atomic.Bool
    35  
    36  	slowLock sync.Mutex
    37  	goGen    GoGen
    38  	cancelGo func()
    39  }
    40  
    41  func NewSlowDetectorThread(slowTyp slowType, nonReturnPeriod time.Duration, goGen GoGen) (sdt *SlowDetectorThread) {
    42  	if goGen == nil {
    43  		panic(perrors.NewPF("goGen cannot be nil"))
    44  	}
    45  
    46  	// dedicated thread case
    47  	if slowTyp != SlowDefault {
    48  		return &SlowDetectorThread{
    49  			slowTyp:         slowTyp,
    50  			nonReturnPeriod: nonReturnPeriod,
    51  			slowMap:         *pmaps.NewRWMap2[slowID, *SlowDetectorInvocation](),
    52  			goGen:           goGen,
    53  		}
    54  	}
    55  
    56  	sdt = &slowDetectorThread
    57  	sdt.slowLock.Lock()
    58  	defer sdt.slowLock.Unlock()
    59  
    60  	if sdt.goGen != nil {
    61  		return // slowDetectorThread already initialized return
    62  	}
    63  
    64  	// slowDetectorThread initialization
    65  	sdt.slowTyp = slowTyp
    66  	sdt.nonReturnPeriod = nonReturnPeriod
    67  	sdt.slowMap = *pmaps.NewRWMap2[slowID, *SlowDetectorInvocation]()
    68  	sdt.goGen = goGen
    69  
    70  	return
    71  }
    72  
    73  func (sdt *SlowDetectorThread) Start(sdi *SlowDetectorInvocation) {
    74  
    75  	// store in map
    76  	sdt.slowMap.Put(sdi.sID, sdi)
    77  
    78  	if !sdt.hasThread.CompareAndSwap(false, true) {
    79  		return // thread already running return
    80  	}
    81  
    82  	// launch thread
    83  	subGo := sdt.goGen.SubGo()
    84  	g0 := subGo.Go()
    85  	go sdt.thread(g0)
    86  	if sdt.slowTyp != SlowShutdownThread {
    87  		return // thread is not to be shutdown return
    88  	}
    89  
    90  	// save cancel method
    91  	sdt.slowLock.Lock()
    92  	defer sdt.slowLock.Unlock()
    93  
    94  	sdt.cancelGo = subGo.Cancel
    95  }
    96  
    97  func (sdt *SlowDetectorThread) Stop(sdi *SlowDetectorInvocation) {
    98  
    99  	// remove from map
   100  	sdt.slowMap.Delete(sdi.sID, parli.MapDeleteWithZeroValue)
   101  
   102  	if sdt.slowMap.Length() > 0 || sdt.slowTyp != SlowShutdownThread {
   103  		return // not to be shutdown or not to be shutdown now return
   104  	}
   105  
   106  	sdt.cancelGo()
   107  }
   108  
   109  func (sdt *SlowDetectorThread) thread(g0 Go) {
   110  	var err error
   111  	defer g0.Register("SlowDetectorThread" + goID().String()).Done(&err)
   112  	defer PanicToErr(&err)
   113  
   114  	ticker := time.NewTicker(slowScanPeriod)
   115  	defer ticker.Stop()
   116  
   117  	var C <-chan time.Time = ticker.C
   118  	var done <-chan struct{} = g0.Context().Done()
   119  	var t time.Time
   120  	for {
   121  		select {
   122  		case <-done:
   123  			return // context cancelled return
   124  		case t = <-C:
   125  		}
   126  
   127  		// check all invocations for non-return
   128  		for _, sdi := range sdt.slowMap.List() {
   129  			// duration is how long the invocation has been in progress
   130  			duration := t.Sub(sdi.t0)
   131  			if duration < 0 {
   132  				// if t coming from the ticker was delayed,
   133  				// then t may be a time in the past,
   134  				// so early that sdi.t0 is after t
   135  				continue // ignore negative durations
   136  			}
   137  			sd := sdi.sd
   138  			sd.alwaysMax.Value(duration)
   139  			if sd.max.Value(duration) {
   140  				// it is a new max, check whether nonReturnPeriod has elapsed
   141  				if tLast := sdi.Time(time.Time{}); tLast.IsZero() || t.Sub(tLast) >= sdt.nonReturnPeriod {
   142  
   143  					// store new nonReturnPeriod start
   144  					sdi.Time(t)
   145  					sd.callback(sdi, false, duration)
   146  				}
   147  			}
   148  		}
   149  	}
   150  }
   151  
   152  type slowType uint8
   153  
   154  func (st slowType) String() (s string) {
   155  	return slowTypeSet.StringT(st)
   156  }
   157  
   158  var slowTypeSet = sets.NewSet[slowType]([]sets.SetElement[slowType]{
   159  	{ValueV: SlowDefault, Name: "sharedThread"},
   160  	{ValueV: SlowOwnThread, Name: "ownThread"},
   161  	{ValueV: SlowShutdownThread, Name: "shutdownThread"},
   162  })