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  }