diff options
Diffstat (limited to 'pkg/tcpip/faketime')
-rw-r--r-- | pkg/tcpip/faketime/BUILD | 21 | ||||
-rw-r--r-- | pkg/tcpip/faketime/faketime.go | 358 | ||||
-rw-r--r-- | pkg/tcpip/faketime/faketime_test.go | 95 |
3 files changed, 0 insertions, 474 deletions
diff --git a/pkg/tcpip/faketime/BUILD b/pkg/tcpip/faketime/BUILD deleted file mode 100644 index bb9d44aff..000000000 --- a/pkg/tcpip/faketime/BUILD +++ /dev/null @@ -1,21 +0,0 @@ -load("//tools:defs.bzl", "go_library", "go_test") - -package(licenses = ["notice"]) - -go_library( - name = "faketime", - srcs = ["faketime.go"], - visibility = ["//visibility:public"], - deps = ["//pkg/tcpip"], -) - -go_test( - name = "faketime_test", - size = "small", - srcs = [ - "faketime_test.go", - ], - deps = [ - "//pkg/tcpip/faketime", - ], -) diff --git a/pkg/tcpip/faketime/faketime.go b/pkg/tcpip/faketime/faketime.go deleted file mode 100644 index fb819d7a8..000000000 --- a/pkg/tcpip/faketime/faketime.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package faketime provides a fake clock that implements tcpip.Clock interface. -package faketime - -import ( - "container/heap" - "fmt" - "sync" - "time" - - "gvisor.dev/gvisor/pkg/tcpip" -) - -// NullClock implements a clock that never advances. -type NullClock struct{} - -var _ tcpip.Clock = (*NullClock)(nil) - -// NowNanoseconds implements tcpip.Clock.NowNanoseconds. -func (*NullClock) NowNanoseconds() int64 { - return 0 -} - -// NowMonotonic implements tcpip.Clock.NowMonotonic. -func (*NullClock) NowMonotonic() int64 { - return 0 -} - -// AfterFunc implements tcpip.Clock.AfterFunc. -func (*NullClock) AfterFunc(time.Duration, func()) tcpip.Timer { - return nil -} - -type notificationChannels struct { - mu struct { - sync.Mutex - - ch []<-chan struct{} - } -} - -func (n *notificationChannels) add(ch <-chan struct{}) { - n.mu.Lock() - defer n.mu.Unlock() - n.mu.ch = append(n.mu.ch, ch) -} - -// wait returns once all the notification channels are readable. -// -// Channels that are added while waiting on existing channels will be waited on -// as well. -func (n *notificationChannels) wait() { - for { - n.mu.Lock() - ch := n.mu.ch - n.mu.ch = nil - n.mu.Unlock() - - if len(ch) == 0 { - break - } - - for _, c := range ch { - <-c - } - } -} - -// ManualClock implements tcpip.Clock and only advances manually with Advance -// method. -type ManualClock struct { - // runningTimers tracks the completion of timer callbacks that began running - // immediately upon their scheduling. It is used to ensure the proper ordering - // of timer callback dispatch. - runningTimers notificationChannels - - mu struct { - sync.RWMutex - - // now is the current (fake) time of the clock. - now time.Time - - // times is min-heap of times. - times timeHeap - - // timers holds the timers scheduled for each time. - timers map[time.Time]map[*manualTimer]struct{} - } -} - -// NewManualClock creates a new ManualClock instance. -func NewManualClock() *ManualClock { - c := &ManualClock{} - - c.mu.Lock() - defer c.mu.Unlock() - - // Set the initial time to a non-zero value since the zero value is used to - // detect inactive timers. - c.mu.now = time.Unix(0, 0) - c.mu.timers = make(map[time.Time]map[*manualTimer]struct{}) - - return c -} - -var _ tcpip.Clock = (*ManualClock)(nil) - -// NowNanoseconds implements tcpip.Clock.NowNanoseconds. -func (mc *ManualClock) NowNanoseconds() int64 { - mc.mu.RLock() - defer mc.mu.RUnlock() - return mc.mu.now.UnixNano() -} - -// NowMonotonic implements tcpip.Clock.NowMonotonic. -func (mc *ManualClock) NowMonotonic() int64 { - return mc.NowNanoseconds() -} - -// AfterFunc implements tcpip.Clock.AfterFunc. -func (mc *ManualClock) AfterFunc(d time.Duration, f func()) tcpip.Timer { - mt := &manualTimer{ - clock: mc, - f: f, - } - - mc.mu.Lock() - defer mc.mu.Unlock() - - mt.mu.Lock() - defer mt.mu.Unlock() - - mc.resetTimerLocked(mt, d) - return mt -} - -// resetTimerLocked schedules a timer to be fired after the given duration. -// -// Precondition: mc.mu and mt.mu must be locked. -func (mc *ManualClock) resetTimerLocked(mt *manualTimer, d time.Duration) { - if !mt.mu.firesAt.IsZero() { - panic("tried to reset an active timer") - } - - t := mc.mu.now.Add(d) - - if !mc.mu.now.Before(t) { - // If the timer is scheduled to fire immediately, call its callback - // in a new goroutine immediately. - // - // It needs to be called in its own goroutine to escape its current - // execution context - like an actual timer. - ch := make(chan struct{}) - mc.runningTimers.add(ch) - - go func() { - defer close(ch) - - mt.f() - }() - - return - } - - mt.mu.firesAt = t - - timers, ok := mc.mu.timers[t] - if !ok { - timers = make(map[*manualTimer]struct{}) - mc.mu.timers[t] = timers - heap.Push(&mc.mu.times, t) - } - - timers[mt] = struct{}{} -} - -// stopTimerLocked stops a timer from firing. -// -// Precondition: mc.mu and mt.mu must be locked. -func (mc *ManualClock) stopTimerLocked(mt *manualTimer) { - t := mt.mu.firesAt - mt.mu.firesAt = time.Time{} - - if t.IsZero() { - panic("tried to stop an inactive timer") - } - - timers, ok := mc.mu.timers[t] - if !ok { - err := fmt.Sprintf("tried to stop an active timer but the clock does not have anything scheduled for the timer @ t = %s %p\nScheduled timers @:", t.UTC(), mt) - for t := range mc.mu.timers { - err += fmt.Sprintf("%s\n", t.UTC()) - } - panic(err) - } - - if _, ok := timers[mt]; !ok { - panic(fmt.Sprintf("did not have an entry in timers for an active timer @ t = %s", t.UTC())) - } - - delete(timers, mt) - - if len(timers) == 0 { - delete(mc.mu.timers, t) - } -} - -// Advance executes all work that have been scheduled to execute within d from -// the current time. Blocks until all work has completed execution. -func (mc *ManualClock) Advance(d time.Duration) { - // We spawn goroutines for timers that were scheduled to fire at the time of - // being reset. Wait for those goroutines to complete before proceeding so - // that timer callbacks are called in the right order. - mc.runningTimers.wait() - - mc.mu.Lock() - defer mc.mu.Unlock() - - until := mc.mu.now.Add(d) - for mc.mu.times.Len() > 0 { - t := heap.Pop(&mc.mu.times).(time.Time) - if t.After(until) { - // No work to do - heap.Push(&mc.mu.times, t) - break - } - - timers := mc.mu.timers[t] - delete(mc.mu.timers, t) - - mc.mu.now = t - - // Mark the timers as inactive since they will be fired. - // - // This needs to be done while holding mc's lock because we remove the entry - // in the map of timers for the current time. If an attempt to stop a - // timer is made after mc's lock was dropped but before the timer is - // marked inactive, we would panic since no entry exists for the time when - // the timer was expected to fire. - for mt := range timers { - mt.mu.Lock() - mt.mu.firesAt = time.Time{} - mt.mu.Unlock() - } - - // Release the lock before calling the timer's callback fn since the - // callback fn might try to schedule a timer which requires obtaining - // mc's lock. - mc.mu.Unlock() - - for mt := range timers { - mt.f() - } - - // The timer callbacks may have scheduled a timer to fire immediately. - // We spawn goroutines for these timers and need to wait for them to - // finish before proceeding so that timer callbacks are called in the - // right order. - mc.runningTimers.wait() - mc.mu.Lock() - } - - mc.mu.now = until -} - -func (mc *ManualClock) resetTimer(mt *manualTimer, d time.Duration) { - mc.mu.Lock() - defer mc.mu.Unlock() - - mt.mu.Lock() - defer mt.mu.Unlock() - - if !mt.mu.firesAt.IsZero() { - mc.stopTimerLocked(mt) - } - - mc.resetTimerLocked(mt, d) -} - -func (mc *ManualClock) stopTimer(mt *manualTimer) bool { - mc.mu.Lock() - defer mc.mu.Unlock() - - mt.mu.Lock() - defer mt.mu.Unlock() - - if mt.mu.firesAt.IsZero() { - return false - } - - mc.stopTimerLocked(mt) - return true -} - -type manualTimer struct { - clock *ManualClock - f func() - - mu struct { - sync.Mutex - - // firesAt is the time when the timer will fire. - // - // Zero only when the timer is not active. - firesAt time.Time - } -} - -var _ tcpip.Timer = (*manualTimer)(nil) - -// Reset implements tcpip.Timer.Reset. -func (mt *manualTimer) Reset(d time.Duration) { - mt.clock.resetTimer(mt, d) -} - -// Stop implements tcpip.Timer.Stop. -func (mt *manualTimer) Stop() bool { - return mt.clock.stopTimer(mt) -} - -type timeHeap []time.Time - -var _ heap.Interface = (*timeHeap)(nil) - -func (h timeHeap) Len() int { - return len(h) -} - -func (h timeHeap) Less(i, j int) bool { - return h[i].Before(h[j]) -} - -func (h timeHeap) Swap(i, j int) { - h[i], h[j] = h[j], h[i] -} - -func (h *timeHeap) Push(x interface{}) { - *h = append(*h, x.(time.Time)) -} - -func (h *timeHeap) Pop() interface{} { - last := (*h)[len(*h)-1] - *h = (*h)[:len(*h)-1] - return last -} diff --git a/pkg/tcpip/faketime/faketime_test.go b/pkg/tcpip/faketime/faketime_test.go deleted file mode 100644 index c2704df2c..000000000 --- a/pkg/tcpip/faketime/faketime_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package faketime_test - -import ( - "testing" - "time" - - "gvisor.dev/gvisor/pkg/tcpip/faketime" -) - -func TestManualClockAdvance(t *testing.T) { - const timeout = time.Millisecond - clock := faketime.NewManualClock() - start := clock.NowMonotonic() - clock.Advance(timeout) - if got, want := time.Duration(clock.NowMonotonic()-start)*time.Nanosecond, timeout; got != want { - t.Errorf("got = %d, want = %d", got, want) - } -} - -func TestManualClockAfterFunc(t *testing.T) { - const ( - timeout1 = time.Millisecond // timeout for counter1 - timeout2 = 2 * time.Millisecond // timeout for counter2 - ) - tests := []struct { - name string - advance time.Duration - wantCounter1 int - wantCounter2 int - }{ - { - name: "before timeout1", - advance: timeout1 - 1, - wantCounter1: 0, - wantCounter2: 0, - }, - { - name: "timeout1", - advance: timeout1, - wantCounter1: 1, - wantCounter2: 0, - }, - { - name: "timeout2", - advance: timeout2, - wantCounter1: 1, - wantCounter2: 1, - }, - { - name: "after timeout2", - advance: timeout2 + 1, - wantCounter1: 1, - wantCounter2: 1, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - clock := faketime.NewManualClock() - counter1 := 0 - counter2 := 0 - clock.AfterFunc(timeout1, func() { - counter1++ - }) - clock.AfterFunc(timeout2, func() { - counter2++ - }) - start := clock.NowMonotonic() - clock.Advance(test.advance) - if got, want := counter1, test.wantCounter1; got != want { - t.Errorf("got counter1 = %d, want = %d", got, want) - } - if got, want := counter2, test.wantCounter2; got != want { - t.Errorf("got counter2 = %d, want = %d", got, want) - } - if got, want := time.Duration(clock.NowMonotonic()-start)*time.Nanosecond, test.advance; got != want { - t.Errorf("got elapsed = %d, want = %d", got, want) - } - }) - } -} |