|  | // Copyright 2009 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // Semaphore implementation exposed to Go. | 
|  | // Intended use is provide a sleep and wakeup | 
|  | // primitive that can be used in the contended case | 
|  | // of other synchronization primitives. | 
|  | // Thus it targets the same goal as Linux's futex, | 
|  | // but it has much simpler semantics. | 
|  | // | 
|  | // That is, don't think of these as semaphores. | 
|  | // Think of them as a way to implement sleep and wakeup | 
|  | // such that every sleep is paired with a single wakeup, | 
|  | // even if, due to races, the wakeup happens before the sleep. | 
|  | // | 
|  | // See Mullender and Cox, ``Semaphores in Plan 9,'' | 
|  | // https://swtch.com/semaphore.pdf | 
|  |  | 
|  | package runtime | 
|  |  | 
|  | import ( | 
|  | "internal/cpu" | 
|  | "runtime/internal/atomic" | 
|  | "unsafe" | 
|  | ) | 
|  |  | 
|  | // Asynchronous semaphore for sync.Mutex. | 
|  |  | 
|  | // A semaRoot holds a balanced tree of sudog with distinct addresses (s.elem). | 
|  | // Each of those sudog may in turn point (through s.waitlink) to a list | 
|  | // of other sudogs waiting on the same address. | 
|  | // The operations on the inner lists of sudogs with the same address | 
|  | // are all O(1). The scanning of the top-level semaRoot list is O(log n), | 
|  | // where n is the number of distinct addresses with goroutines blocked | 
|  | // on them that hash to the given semaRoot. | 
|  | // See golang.org/issue/17953 for a program that worked badly | 
|  | // before we introduced the second level of list, and test/locklinear.go | 
|  | // for a test that exercises this. | 
|  | type semaRoot struct { | 
|  | lock  mutex | 
|  | treap *sudog // root of balanced tree of unique waiters. | 
|  | nwait uint32 // Number of waiters. Read w/o the lock. | 
|  | } | 
|  |  | 
|  | // Prime to not correlate with any user patterns. | 
|  | const semTabSize = 251 | 
|  |  | 
|  | var semtable [semTabSize]struct { | 
|  | root semaRoot | 
|  | pad  [cpu.CacheLinePadSize - unsafe.Sizeof(semaRoot{})]byte | 
|  | } | 
|  |  | 
|  | //go:linkname sync_runtime_Semacquire sync.runtime_Semacquire | 
|  | func sync_runtime_Semacquire(addr *uint32) { | 
|  | semacquire1(addr, false, semaBlockProfile, 0) | 
|  | } | 
|  |  | 
|  | //go:linkname poll_runtime_Semacquire internal/poll.runtime_Semacquire | 
|  | func poll_runtime_Semacquire(addr *uint32) { | 
|  | semacquire1(addr, false, semaBlockProfile, 0) | 
|  | } | 
|  |  | 
|  | //go:linkname sync_runtime_Semrelease sync.runtime_Semrelease | 
|  | func sync_runtime_Semrelease(addr *uint32, handoff bool, skipframes int) { | 
|  | semrelease1(addr, handoff, skipframes) | 
|  | } | 
|  |  | 
|  | //go:linkname sync_runtime_SemacquireMutex sync.runtime_SemacquireMutex | 
|  | func sync_runtime_SemacquireMutex(addr *uint32, lifo bool, skipframes int) { | 
|  | semacquire1(addr, lifo, semaBlockProfile|semaMutexProfile, skipframes) | 
|  | } | 
|  |  | 
|  | //go:linkname poll_runtime_Semrelease internal/poll.runtime_Semrelease | 
|  | func poll_runtime_Semrelease(addr *uint32) { | 
|  | semrelease(addr) | 
|  | } | 
|  |  | 
|  | func readyWithTime(s *sudog, traceskip int) { | 
|  | if s.releasetime != 0 { | 
|  | s.releasetime = cputicks() | 
|  | } | 
|  | goready(s.g, traceskip) | 
|  | } | 
|  |  | 
|  | type semaProfileFlags int | 
|  |  | 
|  | const ( | 
|  | semaBlockProfile semaProfileFlags = 1 << iota | 
|  | semaMutexProfile | 
|  | ) | 
|  |  | 
|  | // Called from runtime. | 
|  | func semacquire(addr *uint32) { | 
|  | semacquire1(addr, false, 0, 0) | 
|  | } | 
|  |  | 
|  | func semacquire1(addr *uint32, lifo bool, profile semaProfileFlags, skipframes int) { | 
|  | gp := getg() | 
|  | if gp != gp.m.curg { | 
|  | throw("semacquire not on the G stack") | 
|  | } | 
|  |  | 
|  | // Easy case. | 
|  | if cansemacquire(addr) { | 
|  | return | 
|  | } | 
|  |  | 
|  | // Harder case: | 
|  | //	increment waiter count | 
|  | //	try cansemacquire one more time, return if succeeded | 
|  | //	enqueue itself as a waiter | 
|  | //	sleep | 
|  | //	(waiter descriptor is dequeued by signaler) | 
|  | s := acquireSudog() | 
|  | root := semroot(addr) | 
|  | t0 := int64(0) | 
|  | s.releasetime = 0 | 
|  | s.acquiretime = 0 | 
|  | s.ticket = 0 | 
|  | if profile&semaBlockProfile != 0 && blockprofilerate > 0 { | 
|  | t0 = cputicks() | 
|  | s.releasetime = -1 | 
|  | } | 
|  | if profile&semaMutexProfile != 0 && mutexprofilerate > 0 { | 
|  | if t0 == 0 { | 
|  | t0 = cputicks() | 
|  | } | 
|  | s.acquiretime = t0 | 
|  | } | 
|  | for { | 
|  | lockWithRank(&root.lock, lockRankRoot) | 
|  | // Add ourselves to nwait to disable "easy case" in semrelease. | 
|  | atomic.Xadd(&root.nwait, 1) | 
|  | // Check cansemacquire to avoid missed wakeup. | 
|  | if cansemacquire(addr) { | 
|  | atomic.Xadd(&root.nwait, -1) | 
|  | unlock(&root.lock) | 
|  | break | 
|  | } | 
|  | // Any semrelease after the cansemacquire knows we're waiting | 
|  | // (we set nwait above), so go to sleep. | 
|  | root.queue(addr, s, lifo) | 
|  | goparkunlock(&root.lock, waitReasonSemacquire, traceEvGoBlockSync, 4+skipframes) | 
|  | if s.ticket != 0 || cansemacquire(addr) { | 
|  | break | 
|  | } | 
|  | } | 
|  | if s.releasetime > 0 { | 
|  | blockevent(s.releasetime-t0, 3+skipframes) | 
|  | } | 
|  | releaseSudog(s) | 
|  | } | 
|  |  | 
|  | func semrelease(addr *uint32) { | 
|  | semrelease1(addr, false, 0) | 
|  | } | 
|  |  | 
|  | func semrelease1(addr *uint32, handoff bool, skipframes int) { | 
|  | root := semroot(addr) | 
|  | atomic.Xadd(addr, 1) | 
|  |  | 
|  | // Easy case: no waiters? | 
|  | // This check must happen after the xadd, to avoid a missed wakeup | 
|  | // (see loop in semacquire). | 
|  | if atomic.Load(&root.nwait) == 0 { | 
|  | return | 
|  | } | 
|  |  | 
|  | // Harder case: search for a waiter and wake it. | 
|  | lockWithRank(&root.lock, lockRankRoot) | 
|  | if atomic.Load(&root.nwait) == 0 { | 
|  | // The count is already consumed by another goroutine, | 
|  | // so no need to wake up another goroutine. | 
|  | unlock(&root.lock) | 
|  | return | 
|  | } | 
|  | s, t0 := root.dequeue(addr) | 
|  | if s != nil { | 
|  | atomic.Xadd(&root.nwait, -1) | 
|  | } | 
|  | unlock(&root.lock) | 
|  | if s != nil { // May be slow or even yield, so unlock first | 
|  | acquiretime := s.acquiretime | 
|  | if acquiretime != 0 { | 
|  | mutexevent(t0-acquiretime, 3+skipframes) | 
|  | } | 
|  | if s.ticket != 0 { | 
|  | throw("corrupted semaphore ticket") | 
|  | } | 
|  | if handoff && cansemacquire(addr) { | 
|  | s.ticket = 1 | 
|  | } | 
|  | readyWithTime(s, 5+skipframes) | 
|  | if s.ticket == 1 && getg().m.locks == 0 { | 
|  | // Direct G handoff | 
|  | // readyWithTime has added the waiter G as runnext in the | 
|  | // current P; we now call the scheduler so that we start running | 
|  | // the waiter G immediately. | 
|  | // Note that waiter inherits our time slice: this is desirable | 
|  | // to avoid having a highly contended semaphore hog the P | 
|  | // indefinitely. goyield is like Gosched, but it emits a | 
|  | // "preempted" trace event instead and, more importantly, puts | 
|  | // the current G on the local runq instead of the global one. | 
|  | // We only do this in the starving regime (handoff=true), as in | 
|  | // the non-starving case it is possible for a different waiter | 
|  | // to acquire the semaphore while we are yielding/scheduling, | 
|  | // and this would be wasteful. We wait instead to enter starving | 
|  | // regime, and then we start to do direct handoffs of ticket and | 
|  | // P. | 
|  | // See issue 33747 for discussion. | 
|  | goyield() | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func semroot(addr *uint32) *semaRoot { | 
|  | return &semtable[(uintptr(unsafe.Pointer(addr))>>3)%semTabSize].root | 
|  | } | 
|  |  | 
|  | func cansemacquire(addr *uint32) bool { | 
|  | for { | 
|  | v := atomic.Load(addr) | 
|  | if v == 0 { | 
|  | return false | 
|  | } | 
|  | if atomic.Cas(addr, v, v-1) { | 
|  | return true | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // queue adds s to the blocked goroutines in semaRoot. | 
|  | func (root *semaRoot) queue(addr *uint32, s *sudog, lifo bool) { | 
|  | s.g = getg() | 
|  | s.elem = unsafe.Pointer(addr) | 
|  | s.next = nil | 
|  | s.prev = nil | 
|  |  | 
|  | var last *sudog | 
|  | pt := &root.treap | 
|  | for t := *pt; t != nil; t = *pt { | 
|  | if t.elem == unsafe.Pointer(addr) { | 
|  | // Already have addr in list. | 
|  | if lifo { | 
|  | // Substitute s in t's place in treap. | 
|  | *pt = s | 
|  | s.ticket = t.ticket | 
|  | s.acquiretime = t.acquiretime | 
|  | s.parent = t.parent | 
|  | s.prev = t.prev | 
|  | s.next = t.next | 
|  | if s.prev != nil { | 
|  | s.prev.parent = s | 
|  | } | 
|  | if s.next != nil { | 
|  | s.next.parent = s | 
|  | } | 
|  | // Add t first in s's wait list. | 
|  | s.waitlink = t | 
|  | s.waittail = t.waittail | 
|  | if s.waittail == nil { | 
|  | s.waittail = t | 
|  | } | 
|  | t.parent = nil | 
|  | t.prev = nil | 
|  | t.next = nil | 
|  | t.waittail = nil | 
|  | } else { | 
|  | // Add s to end of t's wait list. | 
|  | if t.waittail == nil { | 
|  | t.waitlink = s | 
|  | } else { | 
|  | t.waittail.waitlink = s | 
|  | } | 
|  | t.waittail = s | 
|  | s.waitlink = nil | 
|  | } | 
|  | return | 
|  | } | 
|  | last = t | 
|  | if uintptr(unsafe.Pointer(addr)) < uintptr(t.elem) { | 
|  | pt = &t.prev | 
|  | } else { | 
|  | pt = &t.next | 
|  | } | 
|  | } | 
|  |  | 
|  | // Add s as new leaf in tree of unique addrs. | 
|  | // The balanced tree is a treap using ticket as the random heap priority. | 
|  | // That is, it is a binary tree ordered according to the elem addresses, | 
|  | // but then among the space of possible binary trees respecting those | 
|  | // addresses, it is kept balanced on average by maintaining a heap ordering | 
|  | // on the ticket: s.ticket <= both s.prev.ticket and s.next.ticket. | 
|  | // https://en.wikipedia.org/wiki/Treap | 
|  | // https://faculty.washington.edu/aragon/pubs/rst89.pdf | 
|  | // | 
|  | // s.ticket compared with zero in couple of places, therefore set lowest bit. | 
|  | // It will not affect treap's quality noticeably. | 
|  | s.ticket = fastrand() | 1 | 
|  | s.parent = last | 
|  | *pt = s | 
|  |  | 
|  | // Rotate up into tree according to ticket (priority). | 
|  | for s.parent != nil && s.parent.ticket > s.ticket { | 
|  | if s.parent.prev == s { | 
|  | root.rotateRight(s.parent) | 
|  | } else { | 
|  | if s.parent.next != s { | 
|  | panic("semaRoot queue") | 
|  | } | 
|  | root.rotateLeft(s.parent) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // dequeue searches for and finds the first goroutine | 
|  | // in semaRoot blocked on addr. | 
|  | // If the sudog was being profiled, dequeue returns the time | 
|  | // at which it was woken up as now. Otherwise now is 0. | 
|  | func (root *semaRoot) dequeue(addr *uint32) (found *sudog, now int64) { | 
|  | ps := &root.treap | 
|  | s := *ps | 
|  | for ; s != nil; s = *ps { | 
|  | if s.elem == unsafe.Pointer(addr) { | 
|  | goto Found | 
|  | } | 
|  | if uintptr(unsafe.Pointer(addr)) < uintptr(s.elem) { | 
|  | ps = &s.prev | 
|  | } else { | 
|  | ps = &s.next | 
|  | } | 
|  | } | 
|  | return nil, 0 | 
|  |  | 
|  | Found: | 
|  | now = int64(0) | 
|  | if s.acquiretime != 0 { | 
|  | now = cputicks() | 
|  | } | 
|  | if t := s.waitlink; t != nil { | 
|  | // Substitute t, also waiting on addr, for s in root tree of unique addrs. | 
|  | *ps = t | 
|  | t.ticket = s.ticket | 
|  | t.parent = s.parent | 
|  | t.prev = s.prev | 
|  | if t.prev != nil { | 
|  | t.prev.parent = t | 
|  | } | 
|  | t.next = s.next | 
|  | if t.next != nil { | 
|  | t.next.parent = t | 
|  | } | 
|  | if t.waitlink != nil { | 
|  | t.waittail = s.waittail | 
|  | } else { | 
|  | t.waittail = nil | 
|  | } | 
|  | t.acquiretime = now | 
|  | s.waitlink = nil | 
|  | s.waittail = nil | 
|  | } else { | 
|  | // Rotate s down to be leaf of tree for removal, respecting priorities. | 
|  | for s.next != nil || s.prev != nil { | 
|  | if s.next == nil || s.prev != nil && s.prev.ticket < s.next.ticket { | 
|  | root.rotateRight(s) | 
|  | } else { | 
|  | root.rotateLeft(s) | 
|  | } | 
|  | } | 
|  | // Remove s, now a leaf. | 
|  | if s.parent != nil { | 
|  | if s.parent.prev == s { | 
|  | s.parent.prev = nil | 
|  | } else { | 
|  | s.parent.next = nil | 
|  | } | 
|  | } else { | 
|  | root.treap = nil | 
|  | } | 
|  | } | 
|  | s.parent = nil | 
|  | s.elem = nil | 
|  | s.next = nil | 
|  | s.prev = nil | 
|  | s.ticket = 0 | 
|  | return s, now | 
|  | } | 
|  |  | 
|  | // rotateLeft rotates the tree rooted at node x. | 
|  | // turning (x a (y b c)) into (y (x a b) c). | 
|  | func (root *semaRoot) rotateLeft(x *sudog) { | 
|  | // p -> (x a (y b c)) | 
|  | p := x.parent | 
|  | y := x.next | 
|  | b := y.prev | 
|  |  | 
|  | y.prev = x | 
|  | x.parent = y | 
|  | x.next = b | 
|  | if b != nil { | 
|  | b.parent = x | 
|  | } | 
|  |  | 
|  | y.parent = p | 
|  | if p == nil { | 
|  | root.treap = y | 
|  | } else if p.prev == x { | 
|  | p.prev = y | 
|  | } else { | 
|  | if p.next != x { | 
|  | throw("semaRoot rotateLeft") | 
|  | } | 
|  | p.next = y | 
|  | } | 
|  | } | 
|  |  | 
|  | // rotateRight rotates the tree rooted at node y. | 
|  | // turning (y (x a b) c) into (x a (y b c)). | 
|  | func (root *semaRoot) rotateRight(y *sudog) { | 
|  | // p -> (y (x a b) c) | 
|  | p := y.parent | 
|  | x := y.prev | 
|  | b := x.next | 
|  |  | 
|  | x.next = y | 
|  | y.parent = x | 
|  | y.prev = b | 
|  | if b != nil { | 
|  | b.parent = y | 
|  | } | 
|  |  | 
|  | x.parent = p | 
|  | if p == nil { | 
|  | root.treap = x | 
|  | } else if p.prev == y { | 
|  | p.prev = x | 
|  | } else { | 
|  | if p.next != y { | 
|  | throw("semaRoot rotateRight") | 
|  | } | 
|  | p.next = x | 
|  | } | 
|  | } | 
|  |  | 
|  | // notifyList is a ticket-based notification list used to implement sync.Cond. | 
|  | // | 
|  | // It must be kept in sync with the sync package. | 
|  | type notifyList struct { | 
|  | // wait is the ticket number of the next waiter. It is atomically | 
|  | // incremented outside the lock. | 
|  | wait uint32 | 
|  |  | 
|  | // notify is the ticket number of the next waiter to be notified. It can | 
|  | // be read outside the lock, but is only written to with lock held. | 
|  | // | 
|  | // Both wait & notify can wrap around, and such cases will be correctly | 
|  | // handled as long as their "unwrapped" difference is bounded by 2^31. | 
|  | // For this not to be the case, we'd need to have 2^31+ goroutines | 
|  | // blocked on the same condvar, which is currently not possible. | 
|  | notify uint32 | 
|  |  | 
|  | // List of parked waiters. | 
|  | lock mutex | 
|  | head *sudog | 
|  | tail *sudog | 
|  | } | 
|  |  | 
|  | // less checks if a < b, considering a & b running counts that may overflow the | 
|  | // 32-bit range, and that their "unwrapped" difference is always less than 2^31. | 
|  | func less(a, b uint32) bool { | 
|  | return int32(a-b) < 0 | 
|  | } | 
|  |  | 
|  | // notifyListAdd adds the caller to a notify list such that it can receive | 
|  | // notifications. The caller must eventually call notifyListWait to wait for | 
|  | // such a notification, passing the returned ticket number. | 
|  | //go:linkname notifyListAdd sync.runtime_notifyListAdd | 
|  | func notifyListAdd(l *notifyList) uint32 { | 
|  | // This may be called concurrently, for example, when called from | 
|  | // sync.Cond.Wait while holding a RWMutex in read mode. | 
|  | return atomic.Xadd(&l.wait, 1) - 1 | 
|  | } | 
|  |  | 
|  | // notifyListWait waits for a notification. If one has been sent since | 
|  | // notifyListAdd was called, it returns immediately. Otherwise, it blocks. | 
|  | //go:linkname notifyListWait sync.runtime_notifyListWait | 
|  | func notifyListWait(l *notifyList, t uint32) { | 
|  | lockWithRank(&l.lock, lockRankNotifyList) | 
|  |  | 
|  | // Return right away if this ticket has already been notified. | 
|  | if less(t, l.notify) { | 
|  | unlock(&l.lock) | 
|  | return | 
|  | } | 
|  |  | 
|  | // Enqueue itself. | 
|  | s := acquireSudog() | 
|  | s.g = getg() | 
|  | s.ticket = t | 
|  | s.releasetime = 0 | 
|  | t0 := int64(0) | 
|  | if blockprofilerate > 0 { | 
|  | t0 = cputicks() | 
|  | s.releasetime = -1 | 
|  | } | 
|  | if l.tail == nil { | 
|  | l.head = s | 
|  | } else { | 
|  | l.tail.next = s | 
|  | } | 
|  | l.tail = s | 
|  | goparkunlock(&l.lock, waitReasonSyncCondWait, traceEvGoBlockCond, 3) | 
|  | if t0 != 0 { | 
|  | blockevent(s.releasetime-t0, 2) | 
|  | } | 
|  | releaseSudog(s) | 
|  | } | 
|  |  | 
|  | // notifyListNotifyAll notifies all entries in the list. | 
|  | //go:linkname notifyListNotifyAll sync.runtime_notifyListNotifyAll | 
|  | func notifyListNotifyAll(l *notifyList) { | 
|  | // Fast-path: if there are no new waiters since the last notification | 
|  | // we don't need to acquire the lock. | 
|  | if atomic.Load(&l.wait) == atomic.Load(&l.notify) { | 
|  | return | 
|  | } | 
|  |  | 
|  | // Pull the list out into a local variable, waiters will be readied | 
|  | // outside the lock. | 
|  | lockWithRank(&l.lock, lockRankNotifyList) | 
|  | s := l.head | 
|  | l.head = nil | 
|  | l.tail = nil | 
|  |  | 
|  | // Update the next ticket to be notified. We can set it to the current | 
|  | // value of wait because any previous waiters are already in the list | 
|  | // or will notice that they have already been notified when trying to | 
|  | // add themselves to the list. | 
|  | atomic.Store(&l.notify, atomic.Load(&l.wait)) | 
|  | unlock(&l.lock) | 
|  |  | 
|  | // Go through the local list and ready all waiters. | 
|  | for s != nil { | 
|  | next := s.next | 
|  | s.next = nil | 
|  | readyWithTime(s, 4) | 
|  | s = next | 
|  | } | 
|  | } | 
|  |  | 
|  | // notifyListNotifyOne notifies one entry in the list. | 
|  | //go:linkname notifyListNotifyOne sync.runtime_notifyListNotifyOne | 
|  | func notifyListNotifyOne(l *notifyList) { | 
|  | // Fast-path: if there are no new waiters since the last notification | 
|  | // we don't need to acquire the lock at all. | 
|  | if atomic.Load(&l.wait) == atomic.Load(&l.notify) { | 
|  | return | 
|  | } | 
|  |  | 
|  | lockWithRank(&l.lock, lockRankNotifyList) | 
|  |  | 
|  | // Re-check under the lock if we need to do anything. | 
|  | t := l.notify | 
|  | if t == atomic.Load(&l.wait) { | 
|  | unlock(&l.lock) | 
|  | return | 
|  | } | 
|  |  | 
|  | // Update the next notify ticket number. | 
|  | atomic.Store(&l.notify, t+1) | 
|  |  | 
|  | // Try to find the g that needs to be notified. | 
|  | // If it hasn't made it to the list yet we won't find it, | 
|  | // but it won't park itself once it sees the new notify number. | 
|  | // | 
|  | // This scan looks linear but essentially always stops quickly. | 
|  | // Because g's queue separately from taking numbers, | 
|  | // there may be minor reorderings in the list, but we | 
|  | // expect the g we're looking for to be near the front. | 
|  | // The g has others in front of it on the list only to the | 
|  | // extent that it lost the race, so the iteration will not | 
|  | // be too long. This applies even when the g is missing: | 
|  | // it hasn't yet gotten to sleep and has lost the race to | 
|  | // the (few) other g's that we find on the list. | 
|  | for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next { | 
|  | if s.ticket == t { | 
|  | n := s.next | 
|  | if p != nil { | 
|  | p.next = n | 
|  | } else { | 
|  | l.head = n | 
|  | } | 
|  | if n == nil { | 
|  | l.tail = p | 
|  | } | 
|  | unlock(&l.lock) | 
|  | s.next = nil | 
|  | readyWithTime(s, 4) | 
|  | return | 
|  | } | 
|  | } | 
|  | unlock(&l.lock) | 
|  | } | 
|  |  | 
|  | //go:linkname notifyListCheck sync.runtime_notifyListCheck | 
|  | func notifyListCheck(sz uintptr) { | 
|  | if sz != unsafe.Sizeof(notifyList{}) { | 
|  | print("runtime: bad notifyList size - sync=", sz, " runtime=", unsafe.Sizeof(notifyList{}), "\n") | 
|  | throw("bad notifyList size") | 
|  | } | 
|  | } | 
|  |  | 
|  | //go:linkname sync_nanotime sync.runtime_nanotime | 
|  | func sync_nanotime() int64 { | 
|  | return nanotime() | 
|  | } |