package clist
|
|
|
|
/*
|
|
|
|
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: Removed elements need to DetachPrev or DetachNext consistently
|
|
to ensure garbage collection of removed elements.
|
|
|
|
*/
|
|
|
|
import (
|
|
"sync"
|
|
)
|
|
|
|
/*
|
|
|
|
CElement is an element of a linked-list
|
|
Traversal from a CElement is goroutine-safe.
|
|
|
|
We can't avoid using WaitGroups or for-loops given the documentation
|
|
spec without re-implementing the primitives that already exist in
|
|
golang/sync. Notice that WaitGroup allows many go-routines to be
|
|
simultaneously released, which is what we want. Mutex doesn't do
|
|
this. RWMutex does this, but it's clumsy to use in the way that a
|
|
WaitGroup would be used -- and we'd end up having two RWMutex's for
|
|
prev/next each, which is doubly confusing.
|
|
|
|
sync.Cond would be sort-of useful, but we don't need a write-lock in
|
|
the for-loop. Use sync.Cond when you need serial access to the
|
|
"condition". In our case our condition is if `next != nil || removed`,
|
|
and there's no reason to serialize that condition for goroutines
|
|
waiting on NextWait() (since it's just a read operation).
|
|
|
|
*/
|
|
type CElement struct {
|
|
mtx sync.RWMutex
|
|
prev *CElement
|
|
prevWg *sync.WaitGroup
|
|
next *CElement
|
|
nextWg *sync.WaitGroup
|
|
removed bool
|
|
|
|
Value interface{} // immutable
|
|
}
|
|
|
|
// Blocking implementation of Next().
|
|
// May return nil iff CElement was tail and got removed.
|
|
func (e *CElement) NextWait() *CElement {
|
|
for {
|
|
e.mtx.RLock()
|
|
next := e.next
|
|
nextWg := e.nextWg
|
|
removed := e.removed
|
|
e.mtx.RUnlock()
|
|
|
|
if next != nil || removed {
|
|
return next
|
|
}
|
|
|
|
nextWg.Wait()
|
|
// e.next doesn't necessarily exist here.
|
|
// That's why we need to continue a for-loop.
|
|
}
|
|
}
|
|
|
|
// Blocking implementation of Prev().
|
|
// May return nil iff CElement was head and got removed.
|
|
func (e *CElement) PrevWait() *CElement {
|
|
for {
|
|
e.mtx.RLock()
|
|
prev := e.prev
|
|
prevWg := e.prevWg
|
|
removed := e.removed
|
|
e.mtx.RUnlock()
|
|
|
|
if prev != nil || removed {
|
|
return prev
|
|
}
|
|
|
|
prevWg.Wait()
|
|
}
|
|
}
|
|
|
|
// Nonblocking, may return nil if at the end.
|
|
func (e *CElement) Next() *CElement {
|
|
e.mtx.RLock()
|
|
defer e.mtx.RUnlock()
|
|
|
|
return e.next
|
|
}
|
|
|
|
// Nonblocking, may return nil if at the end.
|
|
func (e *CElement) Prev() *CElement {
|
|
e.mtx.RLock()
|
|
defer e.mtx.RUnlock()
|
|
|
|
return e.prev
|
|
}
|
|
|
|
func (e *CElement) Removed() bool {
|
|
e.mtx.RLock()
|
|
defer e.mtx.RUnlock()
|
|
|
|
return e.removed
|
|
}
|
|
|
|
func (e *CElement) DetachNext() {
|
|
if !e.Removed() {
|
|
panic("DetachNext() must be called after Remove(e)")
|
|
}
|
|
e.mtx.Lock()
|
|
defer e.mtx.Unlock()
|
|
|
|
e.next = nil
|
|
}
|
|
|
|
func (e *CElement) DetachPrev() {
|
|
if !e.Removed() {
|
|
panic("DetachPrev() must be called after Remove(e)")
|
|
}
|
|
e.mtx.Lock()
|
|
defer e.mtx.Unlock()
|
|
|
|
e.prev = nil
|
|
}
|
|
|
|
// NOTE: This function needs to be safe for
|
|
// concurrent goroutines waiting on nextWg.
|
|
func (e *CElement) SetNext(newNext *CElement) {
|
|
e.mtx.Lock()
|
|
defer e.mtx.Unlock()
|
|
|
|
oldNext := e.next
|
|
e.next = newNext
|
|
if oldNext != nil && newNext == nil {
|
|
// See https://golang.org/pkg/sync/:
|
|
//
|
|
// If a WaitGroup is reused to wait for several independent sets of
|
|
// 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()
|
|
}
|
|
}
|
|
|
|
// NOTE: This function needs to be safe for
|
|
// concurrent goroutines waiting on prevWg
|
|
func (e *CElement) SetPrev(newPrev *CElement) {
|
|
e.mtx.Lock()
|
|
defer e.mtx.Unlock()
|
|
|
|
oldPrev := e.prev
|
|
e.prev = newPrev
|
|
if oldPrev != nil && newPrev == nil {
|
|
e.prevWg = waitGroup1() // WaitGroups are difficult to re-use.
|
|
}
|
|
if oldPrev == nil && newPrev != nil {
|
|
e.prevWg.Done()
|
|
}
|
|
}
|
|
|
|
func (e *CElement) SetRemoved() {
|
|
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()
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
// CList represents a linked list.
|
|
// The zero value for CList is an empty list ready to use.
|
|
// Operations are goroutine-safe.
|
|
type CList struct {
|
|
mtx sync.RWMutex
|
|
wg *sync.WaitGroup
|
|
head *CElement // first element
|
|
tail *CElement // last element
|
|
len int // list length
|
|
}
|
|
|
|
func (l *CList) Init() *CList {
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
|
|
l.wg = waitGroup1()
|
|
l.head = nil
|
|
l.tail = nil
|
|
l.len = 0
|
|
return l
|
|
}
|
|
|
|
func New() *CList { return new(CList).Init() }
|
|
|
|
func (l *CList) Len() int {
|
|
l.mtx.RLock()
|
|
defer l.mtx.RUnlock()
|
|
|
|
return l.len
|
|
}
|
|
|
|
func (l *CList) Front() *CElement {
|
|
l.mtx.RLock()
|
|
defer l.mtx.RUnlock()
|
|
|
|
return l.head
|
|
}
|
|
|
|
func (l *CList) FrontWait() *CElement {
|
|
// Loop until the head is non-nil else wait and try again
|
|
for {
|
|
l.mtx.RLock()
|
|
head := l.head
|
|
wg := l.wg
|
|
l.mtx.RUnlock()
|
|
|
|
if head != nil {
|
|
return head
|
|
}
|
|
wg.Wait()
|
|
// NOTE: If you think l.head exists here, think harder.
|
|
}
|
|
}
|
|
|
|
func (l *CList) Back() *CElement {
|
|
l.mtx.RLock()
|
|
defer l.mtx.RUnlock()
|
|
|
|
return l.tail
|
|
}
|
|
|
|
func (l *CList) BackWait() *CElement {
|
|
for {
|
|
l.mtx.RLock()
|
|
tail := l.tail
|
|
wg := l.wg
|
|
l.mtx.RUnlock()
|
|
|
|
if tail != nil {
|
|
return tail
|
|
}
|
|
wg.Wait()
|
|
// l.tail doesn't necessarily exist here.
|
|
// That's why we need to continue a for-loop.
|
|
}
|
|
}
|
|
|
|
func (l *CList) PushBack(v interface{}) *CElement {
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
|
|
// Construct a new element
|
|
e := &CElement{
|
|
prev: nil,
|
|
prevWg: waitGroup1(),
|
|
next: nil,
|
|
nextWg: waitGroup1(),
|
|
removed: false,
|
|
Value: v,
|
|
}
|
|
|
|
// Release waiters on FrontWait/BackWait maybe
|
|
if l.len == 0 {
|
|
l.wg.Done()
|
|
}
|
|
l.len += 1
|
|
|
|
// Modify the tail
|
|
if l.tail == nil {
|
|
l.head = e
|
|
l.tail = e
|
|
} else {
|
|
e.SetPrev(l.tail) // We must init e first.
|
|
l.tail.SetNext(e) // This will make e accessible.
|
|
l.tail = e // Update the list.
|
|
}
|
|
|
|
return e
|
|
}
|
|
|
|
// CONTRACT: Caller must call e.DetachPrev() and/or e.DetachNext() to avoid memory leaks.
|
|
// NOTE: As per the contract of CList, removed elements cannot be added back.
|
|
func (l *CList) Remove(e *CElement) interface{} {
|
|
l.mtx.Lock()
|
|
defer l.mtx.Unlock()
|
|
|
|
prev := e.Prev()
|
|
next := e.Next()
|
|
|
|
if l.head == nil || l.tail == nil {
|
|
panic("Remove(e) on empty CList")
|
|
}
|
|
if prev == nil && l.head != e {
|
|
panic("Remove(e) with false head")
|
|
}
|
|
if next == nil && l.tail != e {
|
|
panic("Remove(e) with false tail")
|
|
}
|
|
|
|
// If we're removing the only item, make CList FrontWait/BackWait wait.
|
|
if l.len == 1 {
|
|
l.wg = waitGroup1() // WaitGroups are difficult to re-use.
|
|
}
|
|
|
|
// Update l.len
|
|
l.len -= 1
|
|
|
|
// Connect next/prev and set head/tail
|
|
if prev == nil {
|
|
l.head = next
|
|
} else {
|
|
prev.SetNext(next)
|
|
}
|
|
if next == nil {
|
|
l.tail = prev
|
|
} else {
|
|
next.SetPrev(prev)
|
|
}
|
|
|
|
// Set .Done() on e, otherwise waiters will wait forever.
|
|
e.SetRemoved()
|
|
|
|
return e.Value
|
|
}
|
|
|
|
func waitGroup1() (wg *sync.WaitGroup) {
|
|
wg = &sync.WaitGroup{}
|
|
wg.Add(1)
|
|
return
|
|
}
|