mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-28 20:21:47 +00:00
Add Remove() implementation, CList is doubly-linked
This commit is contained in:
162
clist.go
162
clist.go
@ -2,6 +2,8 @@ package common
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
The purpose of CList is to provide a goroutine-safe linked-list.
|
The purpose of CList is to provide a goroutine-safe linked-list.
|
||||||
|
This list can be traversed concurrently by any number of goroutines.
|
||||||
|
However, removed CElements cannot be added back.
|
||||||
NOTE: Not all methods of container/list are (yet) implemented.
|
NOTE: Not all methods of container/list are (yet) implemented.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -15,23 +17,87 @@ import (
|
|||||||
// Traversal from a CElement are goroutine-safe.
|
// Traversal from a CElement are goroutine-safe.
|
||||||
type CElement struct {
|
type CElement struct {
|
||||||
next unsafe.Pointer
|
next unsafe.Pointer
|
||||||
wg *sync.WaitGroup
|
nextWg *sync.WaitGroup
|
||||||
|
prev unsafe.Pointer
|
||||||
|
prevWg *sync.WaitGroup
|
||||||
|
removed uint32
|
||||||
Value interface{}
|
Value interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking implementation of Next().
|
// Blocking implementation of Next().
|
||||||
// If return is nil, this element was removed from the list.
|
// May return nil iff CElement was tail and got removed.
|
||||||
func (e *CElement) NextWait() *CElement {
|
func (e *CElement) NextWait() *CElement {
|
||||||
e.wg.Wait()
|
for {
|
||||||
return e.Next()
|
e.nextWg.Wait()
|
||||||
|
next := e.Next()
|
||||||
|
if next == nil {
|
||||||
|
if e.Removed() {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CElement) Next() *CElement {
|
// Blocking implementation of Prev().
|
||||||
next := atomic.LoadPointer(&e.next)
|
// May return nil iff CElement was head and got removed.
|
||||||
if next == nil {
|
func (e *CElement) PrevWait() *CElement {
|
||||||
|
for {
|
||||||
|
e.prevWg.Wait()
|
||||||
|
prev := e.Prev()
|
||||||
|
if prev == nil {
|
||||||
|
if e.Removed() {
|
||||||
return nil
|
return nil
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return (*CElement)(next)
|
} else {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nonblocking, may return nil if at the end.
|
||||||
|
func (e *CElement) Next() *CElement {
|
||||||
|
return (*CElement)(atomic.LoadPointer(&e.next))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nonblocking, may return nil if at the end.
|
||||||
|
func (e *CElement) Prev() *CElement {
|
||||||
|
return (*CElement)(atomic.LoadPointer(&e.prev))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CElement) Removed() bool {
|
||||||
|
return atomic.LoadUint32(&(e.removed)) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CElement) setNextAtomic(next *CElement) {
|
||||||
|
oldNext := (*CElement)(atomic.LoadPointer(&e.next))
|
||||||
|
if next == nil && oldNext != nil {
|
||||||
|
e.nextWg.Add(1) // NOTE: There is still a race condition for waiters, so we for-loop.
|
||||||
|
}
|
||||||
|
atomic.StorePointer(&(e.next), unsafe.Pointer(next))
|
||||||
|
if next != nil && oldNext == nil {
|
||||||
|
e.nextWg.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CElement) setPrevAtomic(prev *CElement) {
|
||||||
|
oldPrev := (*CElement)(atomic.LoadPointer(&e.prev))
|
||||||
|
if prev == nil && oldPrev != nil {
|
||||||
|
e.prevWg.Add(1) // NOTE: There is still a race condition for waiters, so we for-loop.
|
||||||
|
}
|
||||||
|
atomic.StorePointer(&(e.prev), unsafe.Pointer(prev))
|
||||||
|
if prev != nil && oldPrev == nil {
|
||||||
|
e.prevWg.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CElement) setRemovedAtomic() {
|
||||||
|
atomic.StoreUint32(&(e.removed), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CList represents a linked list.
|
// CList represents a linked list.
|
||||||
@ -104,45 +170,81 @@ func (l *CList) BackWait() *CElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *CList) PushBack(v interface{}) *CElement {
|
func (l *CList) PushBack(v interface{}) *CElement {
|
||||||
e := &CElement{
|
|
||||||
next: nil,
|
|
||||||
wg: waitGroup1(),
|
|
||||||
Value: v,
|
|
||||||
}
|
|
||||||
l.mtx.Lock()
|
l.mtx.Lock()
|
||||||
defer l.mtx.Unlock()
|
defer l.mtx.Unlock()
|
||||||
|
|
||||||
|
// Construct a new element
|
||||||
|
e := &CElement{
|
||||||
|
prev: nil,
|
||||||
|
prevWg: waitGroup1(),
|
||||||
|
next: nil,
|
||||||
|
nextWg: waitGroup1(),
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release waiters on FrontWait/BackWait maybe
|
||||||
|
if l.len == 0 {
|
||||||
|
l.wg.Done()
|
||||||
|
}
|
||||||
l.len += 1
|
l.len += 1
|
||||||
|
|
||||||
|
// Modify the tail
|
||||||
if l.tail == nil {
|
if l.tail == nil {
|
||||||
l.head = e
|
l.head = e
|
||||||
l.tail = e
|
l.tail = e
|
||||||
l.wg.Done()
|
|
||||||
return e
|
|
||||||
} else {
|
} else {
|
||||||
oldTail := l.tail
|
l.tail.setNextAtomic(e)
|
||||||
atomic.StorePointer(&oldTail.next, unsafe.Pointer(e))
|
e.setPrevAtomic(l.tail)
|
||||||
l.tail = e
|
l.tail = e
|
||||||
oldTail.wg.Done()
|
|
||||||
return e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CList) RemoveFront() interface{} {
|
// NOTE: As per the contract of CList, removed elements cannot be added back.
|
||||||
|
func (l *CList) Remove(e *CElement) interface{} {
|
||||||
l.mtx.Lock()
|
l.mtx.Lock()
|
||||||
defer l.mtx.Unlock()
|
defer l.mtx.Unlock()
|
||||||
if l.head == nil {
|
|
||||||
return nil
|
prev := e.Prev()
|
||||||
|
next := e.Next()
|
||||||
|
|
||||||
|
if l.head == nil || l.tail == nil {
|
||||||
|
PanicSanity("Remove(e) on empty CList")
|
||||||
}
|
}
|
||||||
oldFront := l.head
|
if prev == nil && l.head != e {
|
||||||
next := (*CElement)(oldFront.next)
|
PanicSanity("Remove(e) with false head")
|
||||||
l.head = next
|
}
|
||||||
if next == nil {
|
if next == nil && l.tail != e {
|
||||||
l.tail = nil
|
PanicSanity("Remove(e) with false tail")
|
||||||
l.wg = waitGroup1()
|
}
|
||||||
|
|
||||||
|
// If we're removing the only item, make CList FrontWait/BackWait wait.
|
||||||
|
if l.len == 1 {
|
||||||
|
l.wg.Add(1)
|
||||||
}
|
}
|
||||||
l.len -= 1
|
l.len -= 1
|
||||||
atomic.StorePointer(&oldFront.next, unsafe.Pointer(nil))
|
|
||||||
return oldFront.Value
|
// Modify e.prev and e.next and connect
|
||||||
|
if prev != nil {
|
||||||
|
prev.setNextAtomic(next)
|
||||||
|
}
|
||||||
|
if next != nil {
|
||||||
|
next.setPrevAtomic(prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark e as removed so NextWait/PrevWait can return.
|
||||||
|
e.setRemovedAtomic()
|
||||||
|
|
||||||
|
// Set .Done() on e, otherwise waiters will wait forever.
|
||||||
|
if prev == nil {
|
||||||
|
e.prevWg.Done()
|
||||||
|
}
|
||||||
|
if next == nil {
|
||||||
|
e.nextWg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitGroup1() (wg *sync.WaitGroup) {
|
func waitGroup1() (wg *sync.WaitGroup) {
|
||||||
|
Reference in New Issue
Block a user