mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-30 14:41:20 +00:00
Fix #112 by using RWMutex per element
This commit is contained in:
parent
218acc2224
commit
2fd8f35b74
222
clist/clist.go
222
clist/clist.go
@ -11,36 +11,38 @@ to ensure garbage collection of removed elements.
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CElement is an element of a linked-list
|
// CElement is an element of a linked-list
|
||||||
// Traversal from a CElement are goroutine-safe.
|
// Traversal from a CElement are goroutine-safe.
|
||||||
type CElement struct {
|
type CElement struct {
|
||||||
prev unsafe.Pointer
|
mtx sync.RWMutex
|
||||||
|
prev *CElement
|
||||||
prevWg *sync.WaitGroup
|
prevWg *sync.WaitGroup
|
||||||
next unsafe.Pointer
|
next *CElement
|
||||||
nextWg *sync.WaitGroup
|
nextWg *sync.WaitGroup
|
||||||
removed uint32
|
removed bool
|
||||||
Value interface{}
|
|
||||||
|
Value interface{} // immutable
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking implementation of Next().
|
// Blocking implementation of Next().
|
||||||
// May return nil iff CElement was tail and got removed.
|
// May return nil iff CElement was tail and got removed.
|
||||||
func (e *CElement) NextWait() *CElement {
|
func (e *CElement) NextWait() *CElement {
|
||||||
for {
|
for {
|
||||||
e.nextWg.Wait()
|
e.mtx.RLock()
|
||||||
next := e.Next()
|
next := e.next
|
||||||
if next == nil {
|
nextWg := e.nextWg
|
||||||
if e.Removed() {
|
removed := e.removed
|
||||||
return nil
|
e.mtx.RUnlock()
|
||||||
} else {
|
|
||||||
continue
|
if next != nil || removed {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextWg.Wait()
|
||||||
|
// e.next doesn't necessarily exist here.
|
||||||
|
// That's why we need to continue a for-loop.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,82 +50,113 @@ func (e *CElement) NextWait() *CElement {
|
|||||||
// May return nil iff CElement was head and got removed.
|
// May return nil iff CElement was head and got removed.
|
||||||
func (e *CElement) PrevWait() *CElement {
|
func (e *CElement) PrevWait() *CElement {
|
||||||
for {
|
for {
|
||||||
e.prevWg.Wait()
|
e.mtx.RLock()
|
||||||
prev := e.Prev()
|
prev := e.prev
|
||||||
if prev == nil {
|
prevWg := e.prevWg
|
||||||
if e.Removed() {
|
removed := e.removed
|
||||||
return nil
|
e.mtx.RUnlock()
|
||||||
} else {
|
|
||||||
continue
|
if prev != nil || removed {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevWg.Wait()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nonblocking, may return nil if at the end.
|
// Nonblocking, may return nil if at the end.
|
||||||
func (e *CElement) Next() *CElement {
|
func (e *CElement) Next() *CElement {
|
||||||
return (*CElement)(atomic.LoadPointer(&e.next))
|
e.mtx.RLock()
|
||||||
|
defer e.mtx.RUnlock()
|
||||||
|
|
||||||
|
return e.next
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nonblocking, may return nil if at the end.
|
// Nonblocking, may return nil if at the end.
|
||||||
func (e *CElement) Prev() *CElement {
|
func (e *CElement) Prev() *CElement {
|
||||||
return (*CElement)(atomic.LoadPointer(&e.prev))
|
e.mtx.RLock()
|
||||||
|
defer e.mtx.RUnlock()
|
||||||
|
|
||||||
|
return e.prev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CElement) Removed() bool {
|
func (e *CElement) Removed() bool {
|
||||||
return atomic.LoadUint32(&(e.removed)) > 0
|
e.mtx.RLock()
|
||||||
|
defer e.mtx.RUnlock()
|
||||||
|
|
||||||
|
return e.removed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CElement) DetachNext() {
|
func (e *CElement) DetachNext() {
|
||||||
if !e.Removed() {
|
if !e.Removed() {
|
||||||
panic("DetachNext() must be called after Remove(e)")
|
panic("DetachNext() must be called after Remove(e)")
|
||||||
}
|
}
|
||||||
atomic.StorePointer(&e.next, nil)
|
e.mtx.Lock()
|
||||||
|
defer e.mtx.Unlock()
|
||||||
|
|
||||||
|
e.next = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CElement) DetachPrev() {
|
func (e *CElement) DetachPrev() {
|
||||||
if !e.Removed() {
|
if !e.Removed() {
|
||||||
panic("DetachPrev() must be called after Remove(e)")
|
panic("DetachPrev() must be called after Remove(e)")
|
||||||
}
|
}
|
||||||
atomic.StorePointer(&e.prev, nil)
|
e.mtx.Lock()
|
||||||
|
defer e.mtx.Unlock()
|
||||||
|
|
||||||
|
e.prev = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CElement) setNextAtomic(next *CElement) {
|
// NOTE: This function needs to be safe for
|
||||||
for {
|
// concurrent goroutines waiting on nextWg.
|
||||||
oldNext := atomic.LoadPointer(&e.next)
|
func (e *CElement) SetNext(newNext *CElement) {
|
||||||
if !atomic.CompareAndSwapPointer(&(e.next), oldNext, unsafe.Pointer(next)) {
|
e.mtx.Lock()
|
||||||
continue
|
defer e.mtx.Unlock()
|
||||||
}
|
|
||||||
if next == nil && oldNext != nil { // We for-loop in NextWait() so race is ok
|
oldNext := e.next
|
||||||
e.nextWg.Add(1)
|
e.next = newNext
|
||||||
}
|
if oldNext != nil && newNext == nil {
|
||||||
if next != nil && oldNext == nil {
|
// See https://golang.org/pkg/sync/:
|
||||||
e.nextWg.Done()
|
//
|
||||||
}
|
// If a WaitGroup is reused to wait for several independent sets of
|
||||||
return
|
// events, new Add calls must happen after all previous Wait calls have
|
||||||
|
// returned.
|
||||||
|
e.nextWg = waitGroup1() // WaitGroups are difficult to re-use.
|
||||||
|
}
|
||||||
|
if oldNext == nil && newNext != nil {
|
||||||
|
e.nextWg.Done()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CElement) setPrevAtomic(prev *CElement) {
|
// NOTE: This function needs to be safe for
|
||||||
for {
|
// concurrent goroutines waiting on prevWg
|
||||||
oldPrev := atomic.LoadPointer(&e.prev)
|
func (e *CElement) SetPrev(newPrev *CElement) {
|
||||||
if !atomic.CompareAndSwapPointer(&(e.prev), oldPrev, unsafe.Pointer(prev)) {
|
e.mtx.Lock()
|
||||||
continue
|
defer e.mtx.Unlock()
|
||||||
}
|
|
||||||
if prev == nil && oldPrev != nil { // We for-loop in PrevWait() so race is ok
|
oldPrev := e.prev
|
||||||
e.prevWg.Add(1)
|
e.prev = newPrev
|
||||||
}
|
if oldPrev != nil && newPrev == nil {
|
||||||
if prev != nil && oldPrev == nil {
|
e.prevWg = waitGroup1() // WaitGroups are difficult to re-use.
|
||||||
e.prevWg.Done()
|
}
|
||||||
}
|
if oldPrev == nil && newPrev != nil {
|
||||||
return
|
e.prevWg.Done()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *CElement) setRemovedAtomic() {
|
func (e *CElement) SetRemoved() {
|
||||||
atomic.StoreUint32(&(e.removed), 1)
|
e.mtx.Lock()
|
||||||
|
defer e.mtx.Unlock()
|
||||||
|
|
||||||
|
e.removed = true
|
||||||
|
|
||||||
|
// This wakes up anyone waiting in either direction.
|
||||||
|
if e.prev == nil {
|
||||||
|
e.prevWg.Done()
|
||||||
|
}
|
||||||
|
if e.next == nil {
|
||||||
|
e.nextWg.Done()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------
|
||||||
@ -132,7 +165,7 @@ func (e *CElement) setRemovedAtomic() {
|
|||||||
// The zero value for CList is an empty list ready to use.
|
// The zero value for CList is an empty list ready to use.
|
||||||
// Operations are goroutine-safe.
|
// Operations are goroutine-safe.
|
||||||
type CList struct {
|
type CList struct {
|
||||||
mtx sync.Mutex
|
mtx sync.RWMutex
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
head *CElement // first element
|
head *CElement // first element
|
||||||
tail *CElement // last element
|
tail *CElement // last element
|
||||||
@ -142,6 +175,7 @@ type CList struct {
|
|||||||
func (l *CList) Init() *CList {
|
func (l *CList) Init() *CList {
|
||||||
l.mtx.Lock()
|
l.mtx.Lock()
|
||||||
defer l.mtx.Unlock()
|
defer l.mtx.Unlock()
|
||||||
|
|
||||||
l.wg = waitGroup1()
|
l.wg = waitGroup1()
|
||||||
l.head = nil
|
l.head = nil
|
||||||
l.tail = nil
|
l.tail = nil
|
||||||
@ -152,48 +186,55 @@ func (l *CList) Init() *CList {
|
|||||||
func New() *CList { return new(CList).Init() }
|
func New() *CList { return new(CList).Init() }
|
||||||
|
|
||||||
func (l *CList) Len() int {
|
func (l *CList) Len() int {
|
||||||
l.mtx.Lock()
|
l.mtx.RLock()
|
||||||
defer l.mtx.Unlock()
|
defer l.mtx.RUnlock()
|
||||||
|
|
||||||
return l.len
|
return l.len
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CList) Front() *CElement {
|
func (l *CList) Front() *CElement {
|
||||||
l.mtx.Lock()
|
l.mtx.RLock()
|
||||||
defer l.mtx.Unlock()
|
defer l.mtx.RUnlock()
|
||||||
|
|
||||||
return l.head
|
return l.head
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CList) FrontWait() *CElement {
|
func (l *CList) FrontWait() *CElement {
|
||||||
for {
|
for {
|
||||||
l.mtx.Lock()
|
l.mtx.RLock()
|
||||||
head := l.head
|
head := l.head
|
||||||
wg := l.wg
|
wg := l.wg
|
||||||
l.mtx.Unlock()
|
l.mtx.RUnlock()
|
||||||
if head == nil {
|
|
||||||
wg.Wait()
|
if head != nil {
|
||||||
} else {
|
|
||||||
return head
|
return head
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
// l.head doesn't necessarily exist here.
|
||||||
|
// That's why we need to continue a for-loop.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CList) Back() *CElement {
|
func (l *CList) Back() *CElement {
|
||||||
l.mtx.Lock()
|
l.mtx.RLock()
|
||||||
defer l.mtx.Unlock()
|
defer l.mtx.RUnlock()
|
||||||
|
|
||||||
return l.tail
|
return l.tail
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *CList) BackWait() *CElement {
|
func (l *CList) BackWait() *CElement {
|
||||||
for {
|
for {
|
||||||
l.mtx.Lock()
|
l.mtx.RLock()
|
||||||
tail := l.tail
|
tail := l.tail
|
||||||
wg := l.wg
|
wg := l.wg
|
||||||
l.mtx.Unlock()
|
l.mtx.RUnlock()
|
||||||
if tail == nil {
|
|
||||||
wg.Wait()
|
if tail != nil {
|
||||||
} else {
|
|
||||||
return tail
|
return tail
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
|
// l.tail doesn't necessarily exist here.
|
||||||
|
// That's why we need to continue a for-loop.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,11 +244,12 @@ func (l *CList) PushBack(v interface{}) *CElement {
|
|||||||
|
|
||||||
// Construct a new element
|
// Construct a new element
|
||||||
e := &CElement{
|
e := &CElement{
|
||||||
prev: nil,
|
prev: nil,
|
||||||
prevWg: waitGroup1(),
|
prevWg: waitGroup1(),
|
||||||
next: nil,
|
next: nil,
|
||||||
nextWg: waitGroup1(),
|
nextWg: waitGroup1(),
|
||||||
Value: v,
|
removed: false,
|
||||||
|
Value: v,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release waiters on FrontWait/BackWait maybe
|
// Release waiters on FrontWait/BackWait maybe
|
||||||
@ -221,9 +263,9 @@ func (l *CList) PushBack(v interface{}) *CElement {
|
|||||||
l.head = e
|
l.head = e
|
||||||
l.tail = e
|
l.tail = e
|
||||||
} else {
|
} else {
|
||||||
l.tail.setNextAtomic(e)
|
e.SetPrev(l.tail) // We must init e first.
|
||||||
e.setPrevAtomic(l.tail)
|
l.tail.SetNext(e) // This will make e accessible.
|
||||||
l.tail = e
|
l.tail = e // Update the list.
|
||||||
}
|
}
|
||||||
|
|
||||||
return e
|
return e
|
||||||
@ -250,30 +292,26 @@ func (l *CList) Remove(e *CElement) interface{} {
|
|||||||
|
|
||||||
// If we're removing the only item, make CList FrontWait/BackWait wait.
|
// If we're removing the only item, make CList FrontWait/BackWait wait.
|
||||||
if l.len == 1 {
|
if l.len == 1 {
|
||||||
l.wg.Add(1)
|
l.wg = waitGroup1() // WaitGroups are difficult to re-use.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update l.len
|
||||||
l.len -= 1
|
l.len -= 1
|
||||||
|
|
||||||
// Connect next/prev and set head/tail
|
// Connect next/prev and set head/tail
|
||||||
if prev == nil {
|
if prev == nil {
|
||||||
l.head = next
|
l.head = next
|
||||||
} else {
|
} else {
|
||||||
prev.setNextAtomic(next)
|
prev.SetNext(next)
|
||||||
}
|
}
|
||||||
if next == nil {
|
if next == nil {
|
||||||
l.tail = prev
|
l.tail = prev
|
||||||
} else {
|
} else {
|
||||||
next.setPrevAtomic(prev)
|
next.SetPrev(prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set .Done() on e, otherwise waiters will wait forever.
|
// Set .Done() on e, otherwise waiters will wait forever.
|
||||||
e.setRemovedAtomic()
|
e.SetRemoved()
|
||||||
if prev == nil {
|
|
||||||
e.prevWg.Done()
|
|
||||||
}
|
|
||||||
if next == nil {
|
|
||||||
e.nextWg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.Value
|
return e.Value
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user