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 ( "fmt" "sync" ) // MaxLength is the max allowed number of elements a linked list is // allowed to contain. // If more elements are pushed to the list it will panic. const MaxLength = int(^uint(0) >> 1) /* 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 next *CElement nextWaitCh chan struct{} 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 removed := e.removed signal := e.nextWaitCh e.mtx.RUnlock() if next != nil || removed { return next } <-signal // e.next doesn't necessarily exist here. // That's why we need to continue a for-loop. } } // NextWaitChan can be used to wait until Next becomes not nil. Once it does, // channel will be closed. func (e *CElement) NextWaitChan() <-chan struct{} { e.mtx.RLock() defer e.mtx.RUnlock() return e.nextWaitCh } // Nonblocking, may return nil if at the end. func (e *CElement) Next() *CElement { e.mtx.RLock() val := e.next e.mtx.RUnlock() return val } // Nonblocking, may return nil if at the end. func (e *CElement) Prev() *CElement { e.mtx.RLock() prev := e.prev e.mtx.RUnlock() return prev } func (e *CElement) Removed() bool { e.mtx.RLock() isRemoved := e.removed e.mtx.RUnlock() return isRemoved } func (e *CElement) detachNext() { e.mtx.Lock() if !e.removed { e.mtx.Unlock() panic("DetachNext() must be called after Remove(e)") } e.next = nil e.mtx.Unlock() } func (e *CElement) DetachPrev() { e.mtx.Lock() if !e.removed { e.mtx.Unlock() panic("DetachPrev() must be called after Remove(e)") } e.prev = nil e.mtx.Unlock() } // NOTE: This function needs to be safe for // concurrent goroutines waiting on nextWg. func (e *CElement) setNext(newNext *CElement) { e.mtx.Lock() 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.nextWaitCh = make(chan struct{}) } if oldNext == nil && newNext != nil { close(e.nextWaitCh) } e.mtx.Unlock() } // 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() e.prev = newPrev } func (e *CElement) setRemoved() { e.mtx.Lock() defer e.mtx.Unlock() e.removed = true // This wakes up anyone waiting. if e.next == nil { close(e.nextWaitCh) } } //-------------------------------------------------------------------------------- // CList represents a linked list. // The zero value for CList is an empty list ready to use. // Operations are goroutine-safe. // Panics if length grows beyond the max. type CList struct { mtx sync.RWMutex waitCh chan struct{} head *CElement // first element tail *CElement // last element len int // list length maxLen int // max list length } // Return CList with MaxLength. CList will panic if it goes beyond MaxLength. func New() *CList { return newWithMax(MaxLength) } // Return CList with given maxLength. // Will panic if list exceeds given maxLength. func newWithMax(maxLength int) *CList { l := new(CList) l.maxLen = maxLength l.waitCh = make(chan struct{}) l.head = nil l.tail = nil l.len = 0 return l } func (l *CList) Len() int { l.mtx.RLock() len := l.len l.mtx.RUnlock() return len } func (l *CList) Front() *CElement { l.mtx.RLock() head := l.head l.mtx.RUnlock() return 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 signal := l.waitCh l.mtx.RUnlock() if head != nil { return head } <-signal // NOTE: If you think l.head exists here, think harder. } } func (l *CList) Back() *CElement { l.mtx.RLock() back := l.tail l.mtx.RUnlock() return back } // WaitChan can be used to wait until Front or Back becomes not nil. Once it // does, channel will be closed. func (l *CList) WaitChan() <-chan struct{} { l.mtx.Lock() defer l.mtx.Unlock() return l.waitCh } // Panics if list grows beyond its max length. func (l *CList) PushBack(v interface{}) *CElement { l.mtx.Lock() // Construct a new element e := &CElement{ prev: nil, next: nil, nextWaitCh: make(chan struct{}), removed: false, Value: v, } // Release waiters on FrontWait/BackWait maybe if l.len == 0 { close(l.waitCh) } if l.len >= l.maxLen { panic(fmt.Sprintf("clist: maximum length list reached %d", l.maxLen)) } l.len++ // 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. } l.mtx.Unlock() 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.waitCh = make(chan struct{}) } // Update l.len l.len-- // 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 }