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  }