This repository has been archived by the owner on Feb 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
time.go
123 lines (106 loc) · 3.37 KB
/
time.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package chaos
import (
"context"
"math/rand"
"time"
"github.com/stealthrocket/wasi-go"
)
const (
// Clocks will be reset roughly every minute; note that this is subject to
// fluctuations from the underlying system clocks that the system gets the
// current timestamps from.
driftResetDelay = wasi.Timestamp(time.Minute)
)
// ClockDrift wraps the base system to return one where the clock is drifting
// by a random factor.
func ClockDrift(base wasi.System) wasi.System {
return &clockDriftSystem{System: base}
}
type clockDriftSystem struct {
wasi.System
prng *rand.Rand
drift float64
clock [4]clock
reset []clockSubscription
}
type clock struct {
epoch wasi.Timestamp
errno wasi.Errno
}
type clockSubscription struct {
i int
s wasi.Subscription
}
func (s *clockDriftSystem) ClockTimeGet(ctx context.Context, id wasi.ClockID, precision wasi.Timestamp) (wasi.Timestamp, wasi.Errno) {
t, errno := s.System.ClockTimeGet(ctx, wasi.Monotonic, 1)
if errno != wasi.ESUCCESS {
// We need the underlying system to support monotonic clocks to
// derive the drift on realtime clocks.
return t, errno
}
i := int(id)
if i < 0 || i >= len(s.clock) {
// We only support the 4 clocks defined in the WASI preview 1
// specification.
return 0, wasi.ENOSYS
}
// When the timestamp is before the next reset point of the clock,
// return the timestamp adjusted by the drift.
if m := &s.clock[wasi.Monotonic]; m.epoch != 0 && t < m.epoch+driftResetDelay {
d := float64(t-m.epoch) * s.drift
c := &s.clock[i]
return c.epoch + wasi.Timestamp(d), c.errno
}
// Lazily allocate the random number generator using the current
// timestmap as seed, this makes for a good-enough seed selection
// without having to explicitly expose configuration. This remains
// deterministic as long as the underlying system is.
if s.prng == nil {
s.prng = rand.New(rand.NewSource(int64(t)))
}
// Each time the clocks get reset we compute a new drift to emulate a
// clock skew correction not being completely accurate.
s.drift = 1.0 + ((0.2 * s.prng.Float64()) - 0.1)
for i := range s.clock {
epoch, errno := s.System.ClockTimeGet(ctx, wasi.ClockID(i), 1)
s.clock[i].epoch = epoch
s.clock[i].errno = errno
}
c := &s.clock[i]
return c.epoch, c.errno
}
func (s *clockDriftSystem) PollOneOff(ctx context.Context, subscriptions []wasi.Subscription, events []wasi.Event) (int, wasi.Errno) {
s.reset = s.reset[:0]
// Apply the reverse drift to absolute clock events found in the
// subscription array since the application may have miscalculated
// those.
for i, sub := range subscriptions {
if sub.EventType != wasi.ClockEvent {
continue
}
clockEvent := sub.GetClock()
if !clockEvent.Flags.Has(wasi.Abstime) {
continue
}
clockID := int(clockEvent.ID)
if clockID < 0 || clockID >= len(s.clock) {
continue
}
c := &s.clock[clockID]
if c.errno != wasi.ESUCCESS {
continue
}
d := float64(clockEvent.Timeout-c.epoch) * -s.drift
clockEvent.Timeout = c.epoch + wasi.Timestamp(d)
subscriptions[i] = wasi.MakeSubscriptionClock(sub.UserData, clockEvent)
s.reset = append(s.reset, clockSubscription{i, sub})
}
defer func() {
// Reset the timeouts we altered so the application does not know that
// we mutated the subscription array.
for _, reset := range s.reset {
subscriptions[reset.i] = reset.s
}
}()
return s.System.PollOneOff(ctx, subscriptions, events)
}