github.com/coyove/sdss@v0.0.0-20231129015646-c2ec58cca6a2/future/clock.go (about) 1 package future 2 3 import ( 4 "fmt" 5 "net" 6 "runtime" 7 "sync/atomic" 8 "time" 9 _ "unsafe" 10 11 "github.com/coyove/sdss/future/chrony" 12 ) 13 14 //go:linkname runtimeNano runtime.nanotime 15 func runtimeNano() int64 16 17 type record struct { 18 Nano int64 19 WallNano int64 20 } 21 22 const ( 23 // timeline -----+--------------+--------------+~~~+--------------+--------+----> 24 // | Block | 25 // +--------------+--------------+~~~+--------------+--------+----> 26 // | ch 0 | ch 1 | | ch 11 | | 27 // +--------------+--------------+~~~+--------------+ | 28 // | Window | Window | | Window | cookie | 29 // +-----+--------+-----+--------+~~~+-----+--------+ | 30 // |lo|hi| Margin |lo|hi| Margin | |lo|hi| Margin | | 31 // +-----+--------+-----+--------+~~~+-----+--------+--------+ 32 Block int64 = 100e6 // 10 blocks in one second 33 channelWidth int64 = 8.32e6 // 12 channels in one block 34 Channels int64 = Block / channelWidth // total number of channels 35 Margin int64 = 8.20e6 // NTP/PTP error must be less than half of +/-margin ms 36 hi int64 = (channelWidth - Margin) / 2 // 'hi' stores the timestamps of the current channel of curent block 37 lo int64 = (channelWidth - Margin) / 2 // 'lo' stores the overflowed timestamps of the same channel from previous block 38 cookie int64 = Block - Channels*channelWidth // 0.16ms 39 ) 40 41 type channelState struct { 42 last int64 43 ctr atomic.Int64 44 } 45 46 var ( 47 startup atomic.Pointer[record] 48 atoms [Channels]atomic.Pointer[channelState] 49 Chrony atomic.Pointer[chrony.ReplySourceStats] 50 Base atomic.Pointer[chrony.ReplySourceStats] 51 ) 52 53 var test bool 54 55 func init() { 56 reloadWallClock() 57 } 58 59 func reloadWallClock() { 60 r := &record{ 61 Nano: runtimeNano(), 62 WallNano: time.Now().UnixNano(), 63 } 64 startup.Store(r) 65 } 66 67 func StartWatcher(onError func(error)) { 68 if runtime.GOOS != "linux" { 69 onError(fmt.Errorf("OS not supported, assume correct clock")) 70 Base.CompareAndSwap(nil, &chrony.ReplySourceStats{}) 71 return 72 } 73 74 defer func() { 75 if r := recover(); r != nil { 76 if err, ok := r.(error); ok { 77 onError(err) 78 } else { 79 onError(fmt.Errorf("watcher panic: %v", r)) 80 } 81 } 82 time.AfterFunc(time.Second*5, func() { StartWatcher(onError) }) 83 }() 84 85 conn, err := net.Dial("udp", ":323") 86 if err != nil { 87 onError(err) 88 return 89 } 90 defer conn.Close() 91 92 client := &chrony.Client{Connection: conn, Sequence: 1} 93 resp, err := client.Communicate(chrony.NewTrackingPacket()) 94 if err != nil { 95 onError(err) 96 return 97 } 98 refId := resp.(*chrony.ReplyTracking).RefID 99 100 for i := 0; ; i++ { 101 resp, err := client.Communicate(chrony.NewSourceStatsPacket(int32(i))) 102 if err != nil { 103 break 104 } 105 106 data := resp.(*chrony.ReplySourceStats) 107 if data.RefID == refId { 108 if int64(data.EstimatedOffsetErr*1e9) > Margin/2 { 109 onError(fmt.Errorf("bad NTP clock %v, estimated error %v > %v", 110 data.IPAddr, 111 time.Duration(data.EstimatedOffsetErr*1e9), time.Duration(Margin/2))) 112 } else { 113 Base.CompareAndSwap(nil, data) 114 115 diff := time.Now().UnixNano() - UnixNano() 116 if !(-1e6 < diff && diff < 1e6) { 117 onError(fmt.Errorf("mono clock differs from wall clock: %v", time.Duration(diff))) 118 reloadWallClock() 119 } 120 } 121 Chrony.Store(data) 122 return 123 } 124 } 125 126 Chrony.Store(nil) 127 onError(fmt.Errorf("can't get source stats from chronyd")) 128 } 129 130 func UnixNano() int64 { 131 r := startup.Load() 132 return runtimeNano() - r.Nano + r.WallNano 133 } 134 135 func Now() time.Time { 136 return time.Unix(0, UnixNano()) 137 } 138 139 type Future int64 140 141 func Get(ch int64) Future { 142 if ch < 0 || ch >= Channels { 143 panic(fmt.Sprintf("invalid channel %d, out of range [0, %d)", ch, Channels)) 144 } 145 if data := Base.Load(); data == nil { 146 panic(fmt.Sprintf("bad NTP clock")) 147 } 148 149 ts := UnixNano()/Block*Block + ch*channelWidth 150 151 if old := atoms[ch].Load(); old == nil || ts != old.last { 152 // fmt.Println(ch, old, ts) 153 if atoms[ch].CompareAndSwap(old, &channelState{ 154 last: ts, 155 // ctr is 0, 156 }) { 157 if test { 158 time.Sleep(time.Millisecond * 10) 159 } 160 } 161 } 162 163 upper := ts + channelWidth 164 ts += atoms[ch].Load().ctr.Add(1) 165 166 if ts >= upper-Margin-lo { 167 panic(fmt.Sprintf("too many requests in %dms: %d >= %d - %d - %d", 168 channelWidth/1e6, ts, upper, Margin, lo)) 169 } 170 171 if UnixNano() < upper { 172 return Future(ts + lo) 173 } 174 175 for ts < UnixNano() { 176 ts += Block 177 } 178 return Future(ts) 179 } 180 181 func (f Future) Wait() { 182 diff := time.Duration(int64(f) - UnixNano()) 183 time.Sleep(diff) 184 } 185 186 func (f Future) Channel() int64 { 187 ms := (int64(f) / 1e6) % 1e3 188 groupIdx := ms % (Block / 1e6) 189 return groupIdx / (channelWidth / 1e6) 190 } 191 192 func (f Future) Cookie() (uint16, bool) { 193 next := int64(f)/Block*Block + Block 194 if int64(f) >= next-cookie && int64(f) < next { 195 return uint16(next - int64(f) - 1), true 196 } 197 return 0, false 198 } 199 200 func (f Future) ToCookie(c uint16) Future { 201 grouped := int64(f) / Block * Block 202 return Future(grouped + Block - 1 - int64(c)) 203 }