Source File
clock.go
Belonging Package
go.uber.org/fx/internal/fxclock
// Copyright (c) 2024 Uber Technologies, Inc.//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.package fxclockimport ()// Clock defines how Fx accesses time.// We keep the interface pretty minimal.type Clock interface {Now() time.TimeSince(time.Time) time.DurationSleep(time.Duration)WithTimeout(context.Context, time.Duration) (context.Context, context.CancelFunc)}// System is the default implementation of Clock based on real time.var System Clock = systemClock{}type systemClock struct{}func (systemClock) () time.Time {return time.Now()}func (systemClock) ( time.Time) time.Duration {return time.Since()}func (systemClock) ( time.Duration) {time.Sleep()}func (systemClock) ( context.Context, time.Duration) (context.Context, context.CancelFunc) {return context.WithTimeout(, )}// Mock adapted from// https://github.com/uber-go/zap/blob/7db06bc9b095571d3dc3d4eebdfbe4dd9bd20405/internal/ztest/clock.go.// Mock is a fake source of time.// It implements standard time operations,// but allows the user to control the passage of time.//// Use the [Add] method to progress time.type Mock struct {mu sync.RWMutexnow time.Time// The MockClock works by maintaining a list of waiters.// Each waiter knows the time at which it should be resolved.// When the clock advances, all waiters that are in range are resolved// in chronological order.waiters []waiterwaiterAdded *sync.Cond}var _ Clock = (*Mock)(nil)// NewMock builds a new mock clock// using the current actual time as the initial time.func () *Mock {:= &Mock{now: time.Now()}.waiterAdded = sync.NewCond(&.mu)return}// Now reports the current time.func ( *Mock) () time.Time {.mu.RLock()defer .mu.RUnlock()return .now}// Since reports the time elapsed since t.// This is short for Now().Sub(t).func ( *Mock) ( time.Time) time.Duration {return .Now().Sub()}// Sleep pauses the current goroutine for the given duration.//// With the mock clock, this will freeze// until the clock is advanced with [Add] past the deadline.func ( *Mock) ( time.Duration) {:= make(chan struct{}).runAt(.Now().Add(), func() { close() })<-}// WithTimeout returns a new context with a deadline of now + d.//// When the deadline is passed, the returned context's Done channel is closed// and the context's Err method returns context.DeadlineExceeded.// If the cancel function is called before the deadline is passed,// the context's Err method returns context.Canceled.func ( *Mock) ( context.Context, time.Duration) (context.Context, context.CancelFunc) {// Unfortunately, we can't use context.WithCancelCause here.// Per its documentation (and verified by trying it)://// ctx, cancel := context.WithCancelCause(parent)// cancel(myError)// ctx.Err() // returns context.Canceled// context.Cause(ctx) // returns myError//// So it won't do for our purposes.:= .Now().Add(), := context.WithCancel():= &deadlineCtx{inner: ,cancelInner: ,done: make(chan struct{}),deadline: ,}=.runAt(, func() {.cancel(context.DeadlineExceeded)})return , func() { .cancel(context.Canceled) }}type deadlineCtx struct {inner context.ContextcancelInner func()done chan struct{}deadline time.Timemu sync.Mutex // guards err; the rest is immutableerr error}var _ context.Context = (*deadlineCtx)(nil)func ( *deadlineCtx) () ( time.Time, bool) { return .deadline, true }func ( *deadlineCtx) () <-chan struct{} { return .done }func ( *deadlineCtx) ( any) any { return .inner.Value() }func ( *deadlineCtx) () error {.mu.Lock()defer .mu.Unlock()return .err}func ( *deadlineCtx) ( error) {.mu.Lock()if .err == nil {.err =close(.done).cancelInner()}.mu.Unlock()}// runAt schedules the given function to be run at the given time.// The function runs without a lock held, so it may schedule more work.func ( *Mock) ( time.Time, func()) {.mu.Lock()defer .mu.Unlock().waiters = append(.waiters, waiter{until: , fn: }).waiterAdded.Broadcast()}// AwaitScheduled blocks until there are at least N// operations scheduled for the future.func ( *Mock) ( int) {.mu.Lock()defer .mu.Unlock()// Note: waiterAdded is associated with c.mu,// the same lock we're holding here.//// When we call Wait(), it'll release the lock// and block until signaled by runAt,// at which point it'll reacquire the lock// (waiting until runAt has released it).for len(.waiters) < {.waiterAdded.Wait()}}type waiter struct {until time.Timefn func()}// Add progresses time by the given duration.// Other operations waiting for the time to advance// will be resolved if they are within range.//// Side effects of operations waiting for the time to advance// will take effect on a best-effort basis.// Avoid racing with operations that have side effects.//// Panics if the duration is negative.func ( *Mock) ( time.Duration) {if < 0 {panic("cannot add negative duration")}.mu.Lock()defer .mu.Unlock()sort.Slice(.waiters, func(, int) bool {return .waiters[].until.Before(.waiters[].until)}):= .now.Add()// newTime won't be recorded until the end of this method.// This ensures that any waiters that are resolved// are resolved at the time they were expecting.for len(.waiters) > 0 {:= .waiters[0]if .until.After() {break}.waiters[0] = waiter{} // avoid memory leak.waiters = .waiters[1:]// The waiter is within range.// Travel to the time of the waiter and resolve it..now = .until// The waiter may schedule more work// so we must release the lock..mu.Unlock().fn()// Sleeping here is necessary to let the side effects of waiters// take effect before we continue.time.Sleep(1 * time.Millisecond).mu.Lock()}.now =}
![]() |
The pages are generated with Golds v0.8.2. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |