github.com/loov/combiner@v0.1.0/extcombiner/basic_spinning_uintptr.go (about)

     1  package extcombiner
     2  
     3  import (
     4  	"sync/atomic"
     5  	"unsafe"
     6  )
     7  
     8  // BasicSpinningUintptr is an unbounded spinning combiner queue using uintptr internally
     9  //
    10  // Based on https://software.intel.com/en-us/blogs/2013/02/22/combineraggregator-synchronization-primitive
    11  type BasicSpinningUintptr struct {
    12  	head    uintptr // *basicSpinningUintptrNode
    13  	_       [7]uint64
    14  	batcher Batcher
    15  }
    16  
    17  type basicSpinningUintptrNode struct {
    18  	next     uintptr // *basicSpinningUintptrNode
    19  	argument interface{}
    20  }
    21  
    22  // NewBasicSpinningUintptr creates a BasicSpinningUintptr queue.
    23  func NewBasicSpinningUintptr(batcher Batcher) *BasicSpinningUintptr {
    24  	return &BasicSpinningUintptr{
    25  		batcher: batcher,
    26  		head:    0,
    27  	}
    28  }
    29  
    30  const basicSpinningUintptrLocked = uintptr(1)
    31  
    32  // Do passes value to Batcher and waits for completion
    33  func (c *BasicSpinningUintptr) Do(op interface{}) {
    34  	node := &basicSpinningUintptrNode{argument: op}
    35  
    36  	// c.head can be in 3 states:
    37  	// c.head == 0: no operations in-flight, initial state.
    38  	// c.head == LOCKED: single operation in-flight, no combining opportunities.
    39  	// c.head == pointer to some basicSpinningUintptrNode that is subject to combining.
    40  	//            The args are chainded into a lock-free list via 'next' fields.
    41  	// node.next also can be in 3 states:
    42  	// node.next == pointer to other node.
    43  	// node.next == LOCKED: denotes the last node in the list.
    44  	// node.next == 0: the operation is finished.
    45  
    46  	// The function proceeds in 3 steps:
    47  	// 1. If c.head == nil, exchange it to LOCKED and become the combiner.
    48  	// Otherwise, enqueue own node into the c->head lock-free list.
    49  
    50  	var cmp uintptr
    51  	for {
    52  		cmp = atomic.LoadUintptr(&c.head)
    53  		xchg := basicSpinningUintptrLocked
    54  		if cmp != 0 {
    55  			// There is already a combiner, enqueue itself.
    56  			xchg = uintptr(unsafe.Pointer(node))
    57  			node.next = cmp
    58  		}
    59  
    60  		if atomic.CompareAndSwapUintptr(&c.head, cmp, xchg) {
    61  			break
    62  		}
    63  	}
    64  
    65  	if cmp != 0 {
    66  		// 2. If we are not the combiner, wait for node.next to become nil
    67  		// (which means the operation is finished).
    68  		for try := 0; atomic.LoadUintptr(&node.next) != 0; spin(&try) {
    69  		}
    70  	} else {
    71  		// 3. We are the combiner.
    72  
    73  		// First, execute own operation.
    74  		c.batcher.Start()
    75  		defer c.batcher.Finish()
    76  
    77  		c.batcher.Do(node.argument)
    78  
    79  		// Then, look for combining opportunities.
    80  		for {
    81  			for {
    82  				cmp = atomic.LoadUintptr(&c.head)
    83  				// If there are some operations in the list,
    84  				// grab the list and replace with LOCKED.
    85  				// Otherwise, exchange to nil.
    86  				var xchg uintptr = 0
    87  				if cmp != basicSpinningUintptrLocked {
    88  					xchg = basicSpinningUintptrLocked
    89  				}
    90  
    91  				if atomic.CompareAndSwapUintptr(&c.head, cmp, xchg) {
    92  					break
    93  				}
    94  			}
    95  
    96  			// No more operations to combine, return.
    97  			if cmp == basicSpinningUintptrLocked {
    98  				break
    99  			}
   100  
   101  			// Execute the list of operations.
   102  			for cmp != basicSpinningUintptrLocked {
   103  				node = (*basicSpinningUintptrNode)(unsafe.Pointer(cmp))
   104  				cmp = node.next
   105  
   106  				c.batcher.Do(node.argument)
   107  				// Mark completion.
   108  				atomic.StoreUintptr(&node.next, 0)
   109  			}
   110  		}
   111  	}
   112  }