github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/spanlatch/signal.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package spanlatch 12 13 import ( 14 "sync/atomic" 15 "unsafe" 16 ) 17 18 const ( 19 // not yet signaled. 20 noSig int32 = iota 21 // signaled and the channel was not closed. 22 sig 23 // signaled and the channel was closed. 24 sigClosed 25 ) 26 27 // signal is a type that can signal the completion of an operation. 28 // 29 // The type has three benefits over using a channel directly and 30 // closing the channel when the operation completes: 31 // 1. signaled() uses atomics to provide a fast-path for checking 32 // whether the operation has completed. It is ~75x faster than 33 // using a channel for this purpose. 34 // 2. the receiver's channel is lazily initialized when signalChan() 35 // is called, avoiding the allocation when one is not needed. 36 // 3. because of 2, the type's zero value can be used directly. 37 // 38 type signal struct { 39 a int32 40 c unsafe.Pointer // chan struct{}, lazily initialized 41 } 42 43 func (s *signal) signal() { 44 if !atomic.CompareAndSwapInt32(&s.a, noSig, sig) { 45 panic("signaled twice") 46 } 47 // Close the channel if it was ever initialized. 48 if cPtr := atomic.LoadPointer(&s.c); cPtr != nil { 49 // Coordinate with signalChan to avoid double-closing. 50 if atomic.CompareAndSwapInt32(&s.a, sig, sigClosed) { 51 close(ptrToChan(cPtr)) 52 } 53 } 54 } 55 56 func (s *signal) signaled() bool { 57 return atomic.LoadInt32(&s.a) > noSig 58 } 59 60 func (s *signal) signalChan() <-chan struct{} { 61 // If the signal has already been signaled, return a closed channel. 62 if s.signaled() { 63 return closedC 64 } 65 66 // If the signal's channel has already been lazily initialized, return it. 67 if cPtr := atomic.LoadPointer(&s.c); cPtr != nil { 68 return ptrToChan(cPtr) 69 } 70 71 // Lazily initialize the channel. 72 c := make(chan struct{}) 73 if !atomic.CompareAndSwapPointer(&s.c, nil, chanToPtr(c)) { 74 // We raced with another initialization. 75 return ptrToChan(atomic.LoadPointer(&s.c)) 76 } 77 78 // Coordinate with signal to close the new channel, if necessary. 79 if atomic.CompareAndSwapInt32(&s.a, sig, sigClosed) { 80 close(c) 81 } 82 return c 83 } 84 85 func chanToPtr(c chan struct{}) unsafe.Pointer { 86 return *(*unsafe.Pointer)(unsafe.Pointer(&c)) 87 } 88 89 func ptrToChan(p unsafe.Pointer) chan struct{} { 90 return *(*chan struct{})(unsafe.Pointer(&p)) 91 } 92 93 var closedC chan struct{} 94 95 func init() { 96 closedC = make(chan struct{}) 97 close(closedC) 98 }