go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/clock/systemtimer.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package clock
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  )
    21  
    22  type systemTimer struct {
    23  	// ctx is the underlying timer. It starts as nil, and is initialized on Reset.
    24  	ctx context.Context
    25  
    26  	// timerC is the timer channel.
    27  	timerC chan TimerResult
    28  
    29  	// timerStoppedC is a signal channel used by Stop to alert our monitor that
    30  	// this timer has been manually canceled.
    31  	timerStoppedC chan struct{}
    32  	// timerMonitorResultC returns true if the timer monitor was prematurely
    33  	// terminated, false if not. It is used by our timer monitor to indicate its
    34  	// status when stopped.
    35  	timerMonitorResultC chan bool
    36  }
    37  
    38  var _ Timer = (*systemTimer)(nil)
    39  
    40  func newSystemTimer(ctx context.Context) Timer {
    41  	return &systemTimer{
    42  		ctx:    ctx,
    43  		timerC: make(chan TimerResult, 1),
    44  	}
    45  }
    46  
    47  func (t *systemTimer) GetC() <-chan TimerResult {
    48  	return t.timerC
    49  }
    50  
    51  func (t *systemTimer) Reset(d time.Duration) (running bool) {
    52  	running = t.Stop()
    53  
    54  	// If our Context is already done, finish immediately.
    55  	if err := t.ctx.Err(); err != nil {
    56  		t.timerC <- TimerResult{Time: time.Now(), Err: err}
    57  		return
    58  	}
    59  
    60  	// Start a monitor goroutine and our actual timer. Copy our channels, since
    61  	// future stop/reset will change the systemTimer's values and our goroutine
    62  	// should only operate on this round's values.
    63  	t.timerStoppedC = make(chan struct{})
    64  	t.timerMonitorResultC = make(chan bool, 1)
    65  
    66  	timerStoppedC, timerMonitorResultC := t.timerStoppedC, t.timerMonitorResultC
    67  
    68  	realTimer := time.NewTimer(d)
    69  	go func() {
    70  		defer realTimer.Stop()
    71  
    72  		interrupted := false
    73  		defer func() {
    74  			timerMonitorResultC <- interrupted
    75  			close(timerMonitorResultC)
    76  		}()
    77  
    78  		select {
    79  		case <-timerStoppedC:
    80  			interrupted = true
    81  
    82  		case <-t.ctx.Done():
    83  			t.timerC <- TimerResult{Time: time.Now(), Err: t.ctx.Err()}
    84  
    85  		case now := <-realTimer.C:
    86  			t.timerC <- TimerResult{Time: now, Err: nil}
    87  		}
    88  	}()
    89  
    90  	return
    91  }
    92  
    93  func (t *systemTimer) Stop() bool {
    94  	if t.timerStoppedC == nil {
    95  		return false
    96  	}
    97  	close(t.timerStoppedC)
    98  	t.timerStoppedC = nil
    99  	return <-t.timerMonitorResultC
   100  }