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

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