github.com/GeniusesGroup/libgo@v0.0.0-20220929090155-5ff932cb408e/timer/timer-async.go (about)

     1  /* For license and copyright information please see the LEGAL file in the code repository */
     2  
     3  package timer
     4  
     5  import (
     6  	"unsafe"
     7  
     8  	"github.com/GeniusesGroup/libgo/cpu"
     9  	"github.com/GeniusesGroup/libgo/protocol"
    10  	"github.com/GeniusesGroup/libgo/race"
    11  	"github.com/GeniusesGroup/libgo/scheduler"
    12  	"github.com/GeniusesGroup/libgo/time/monotonic"
    13  )
    14  
    15  // NewAsync waits for the duration to elapse and then calls callback.
    16  // If callback need blocking operation it must do its logic in new thread(goroutine).
    17  // It returns a Timer that can be used to cancel the call using its Stop method.
    18  func NewAsync(d protocol.Duration, callback protocol.TimerListener) (t *Async, err protocol.Error) {
    19  	var timer Async
    20  	timer.Init(callback)
    21  	err = timer.Start(d)
    22  	t = &timer
    23  	return
    24  }
    25  
    26  type Async struct {
    27  	// Timer wakes up at when, and then at when+period, ... (period > 0 only)
    28  	// when must be positive on an active timer.
    29  	when   monotonic.Time
    30  	period protocol.Duration
    31  
    32  	// The status field holds one of the values in status file.
    33  	status status
    34  
    35  	// callback function that call when reach
    36  	// it is possible that callback will be called a little after the delay.
    37  	// **NOTE**: each time calling callback() in the timer goroutine, so callback must be
    38  	// a well-behaved function and not block.
    39  	callback protocol.TimerListener
    40  
    41  	timers *TimingHeap
    42  }
    43  
    44  // Init initialize the timer with given callback function or make the channel and send signal on it
    45  // Be aware that given function must not be closure and must not block the caller.
    46  func (t *Async) Init(callback protocol.TimerListener) {
    47  	if t.callback != nil {
    48  		panic("timer: Don't initialize a timer twice. Use Reset() method to change the timer.")
    49  	}
    50  
    51  	t.callback = callback
    52  }
    53  func (t *Async) Reinit() {
    54  	t.callback = nil
    55  	t.timers = nil
    56  }
    57  func (t *Async) Deinit() {
    58  }
    59  
    60  // Start adds the timer to the running cpu core timing.
    61  // This should only be called with a newly created timer.
    62  // That avoids the risk of changing the when field of a timer in some P's heap,
    63  // which could cause the heap to become unsorted.
    64  func (t *Async) Start(d protocol.Duration) (err protocol.Error) {
    65  	if t.callback == nil {
    66  		panic("timer: Timer must initialized before start")
    67  	}
    68  	if t.status != status_Unset {
    69  		panic("timer: start called with started timer")
    70  	}
    71  	if t.timers != nil {
    72  		panic("timer: timers already set in timer")
    73  	}
    74  	// when must be positive. A negative value will cause ts.runTimer to
    75  	// overflow during its delta calculation and never expire other runtime timers.
    76  	// Zero will cause checkTimers to fail to notice the timer.
    77  	if d < 1 {
    78  		panic("timer: timer must have positive duration.")
    79  	}
    80  
    81  	if race.DetectorEnabled {
    82  		race.Release(unsafe.Pointer(t))
    83  	}
    84  	t.when = when(d)
    85  	t.status = status_Waiting
    86  	t.timers = &poolByCores[cpu.ActiveCoreID()]
    87  	t.timers.AddTimer(t)
    88  	return
    89  }
    90  
    91  // Stop deletes the timer t. We can't actually remove it from the timers heap.
    92  // We can only mark it as deleted. It will be removed in due course by the timing whose heap it is on.
    93  // Reports whether the timer was removed before it was run.
    94  func (t *Async) Stop() bool {
    95  	if t.callback == nil {
    96  		panic("timer: Stop called on uninitialized Timer")
    97  	}
    98  
    99  	for {
   100  		var status = t.status.Load()
   101  		switch status {
   102  		case status_Waiting, status_ModifiedLater:
   103  			if t.status.CompareAndSwap(status, status_Modifying) {
   104  				// Must fetch t.timers before changing status,
   105  				// as ts.cleanTimers in another goroutine can clear t.timers of a status_Deleted timer.
   106  				var timers = t.timers
   107  				if !t.status.CompareAndSwap(status_Modifying, status_Deleted) {
   108  					badTimer()
   109  				}
   110  				timers.deletedTimers.Add(1)
   111  				// Timer was not yet run.
   112  				return true
   113  			}
   114  		case status_ModifiedEarlier:
   115  			if t.status.CompareAndSwap(status, status_Modifying) {
   116  				var timers = t.timers
   117  				if !t.status.CompareAndSwap(status_Modifying, status_Deleted) {
   118  					badTimer()
   119  				}
   120  				timers.deletedTimers.Add(1)
   121  				// Timer was not yet run.
   122  				return true
   123  			}
   124  		case status_Deleted, status_Removing, status_Removed:
   125  			// Timer was already run.
   126  			return false
   127  		case status_Running, status_Moving:
   128  			// The timer is being run or moved, by a different P.
   129  			// Wait for it to complete.
   130  			scheduler.Yield(scheduler.Thread_WaitReason_Preempted)
   131  		case status_Unset:
   132  			// Removing timer that was never added or
   133  			// has already been run. Also see issue 21874.
   134  			return false
   135  		case status_Modifying:
   136  			// Simultaneous calls to delete and modify.
   137  			// Wait for the other call to complete.
   138  			scheduler.Yield(scheduler.Thread_WaitReason_Preempted)
   139  		default:
   140  			badTimer()
   141  		}
   142  	}
   143  }
   144  
   145  // Modify modifies an existing timer.
   146  // It's OK to call modify() on a newly allocated Timer.
   147  // Reports whether the timer was modified before it was run.
   148  func (t *Async) Modify(d protocol.Duration) (pending bool) {
   149  	// when must be positive. A negative value will cause ts.runTimer to
   150  	// overflow during its delta calculation and never expire other runtime timers.
   151  	// Zero will cause checkTimers to fail to notice the timer.
   152  	if d < 1 {
   153  		panic("timer: timer must have positive duration")
   154  	}
   155  	if t.callback == nil {
   156  		panic("timer: Timer must initialized before reset")
   157  	}
   158  
   159  	if race.DetectorEnabled {
   160  		race.Release(unsafe.Pointer(t))
   161  	}
   162  
   163  	var wasRemoved = false
   164  loop:
   165  	for {
   166  		var status = t.status.Load()
   167  		switch status {
   168  		case status_Waiting, status_ModifiedEarlier, status_ModifiedLater:
   169  			if t.status.CompareAndSwap(status, status_Modifying) {
   170  				pending = true // timer not yet run
   171  				break loop
   172  			}
   173  		case status_Unset, status_Removed:
   174  			// Timer was already run and t is no longer in a heap.
   175  			// Act like AddTimer.
   176  			if t.status.CompareAndSwap(status, status_Modifying) {
   177  				wasRemoved = true
   178  				pending = false // timer already run or stopped
   179  				break loop
   180  			}
   181  		case status_Deleted:
   182  			if t.status.CompareAndSwap(status, status_Modifying) {
   183  				t.timers.deletedTimers.Add(-1)
   184  				pending = false // timer already stopped
   185  				break loop
   186  			}
   187  		case status_Running, status_Removing, status_Moving:
   188  			// The timer is being run or moved, by a different P.
   189  			// Wait for it to complete.
   190  			scheduler.Yield(scheduler.Thread_WaitReason_Preempted)
   191  		case status_Modifying:
   192  			// Multiple simultaneous calls to modify.
   193  			// Wait for the other call to complete.
   194  			scheduler.Yield(scheduler.Thread_WaitReason_Preempted)
   195  		default:
   196  			badTimer()
   197  		}
   198  	}
   199  
   200  	var timerOldWhen = t.when
   201  	var timerNewWhen = when(d)
   202  	t.when = timerNewWhen
   203  	if t.period != 0 {
   204  		t.period = d
   205  	}
   206  	if wasRemoved {
   207  		t.timers = &poolByCores[cpu.ActiveCoreID()]
   208  		t.timers.AddTimer(t)
   209  		if !t.status.CompareAndSwap(status_Modifying, status_Waiting) {
   210  			badTimer()
   211  		}
   212  	} else {
   213  		var newStatus = status_ModifiedLater
   214  		if timerNewWhen < timerOldWhen {
   215  			newStatus = status_ModifiedEarlier
   216  			t.timers.updateTimerModifiedEarliest(timerNewWhen)
   217  		}
   218  
   219  		// Set the new status of the timer.
   220  		if !t.status.CompareAndSwap(status_Modifying, newStatus) {
   221  			badTimer()
   222  		}
   223  	}
   224  
   225  	return
   226  }
   227  
   228  // Tick will send a signal on the t.Signal() channel after each tick on initialized Timer.
   229  // The period of the ticks is specified by the duration arguments.
   230  // The ticker will adjust the time interval or drop ticks to make up for slow receivers.
   231  // The durations must be greater than zero; if not, Tick() will panic.
   232  // Stop the ticker to release associated resources.
   233  func (t *Async) Tick(first, interval protocol.Duration) (err protocol.Error) {
   234  	if first < 1 || interval < 1 {
   235  		panic("timer: non-positive interval to tick. period must be non-negative,")
   236  	}
   237  	t.period = interval
   238  	t.Start(first)
   239  	return
   240  }