4 The purpose of CList is to provide a goroutine-safe linked-list.
5 This list can be traversed concurrently by any number of goroutines.
6 However, removed CElements cannot be added back.
7 NOTE: Not all methods of container/list are (yet) implemented.
8 NOTE: Removed elements need to DetachPrev or DetachNext consistently
9 to ensure garbage collection of removed elements.
18 // CElement is an element of a linked-list
19 // Traversal from a CElement are goroutine-safe.
20 type CElement struct {
22 prevWg *sync.WaitGroup
24 nextWg *sync.WaitGroup
29 // Blocking implementation of Next().
30 // May return nil iff CElement was tail and got removed.
31 func (e *CElement) NextWait() *CElement {
47 // Blocking implementation of Prev().
48 // May return nil iff CElement was head and got removed.
49 func (e *CElement) PrevWait() *CElement {
65 // Nonblocking, may return nil if at the end.
66 func (e *CElement) Next() *CElement {
67 return (*CElement)(atomic.LoadPointer(&e.next))
70 // Nonblocking, may return nil if at the end.
71 func (e *CElement) Prev() *CElement {
72 return (*CElement)(atomic.LoadPointer(&e.prev))
75 func (e *CElement) Removed() bool {
76 return atomic.LoadUint32(&(e.removed)) > 0
79 func (e *CElement) DetachNext() {
81 panic("DetachNext() must be called after Remove(e)")
83 atomic.StorePointer(&e.next, nil)
86 func (e *CElement) DetachPrev() {
88 panic("DetachPrev() must be called after Remove(e)")
90 atomic.StorePointer(&e.prev, nil)
93 func (e *CElement) setNextAtomic(next *CElement) {
95 oldNext := atomic.LoadPointer(&e.next)
96 if !atomic.CompareAndSwapPointer(&(e.next), oldNext, unsafe.Pointer(next)) {
99 if next == nil && oldNext != nil { // We for-loop in NextWait() so race is ok
102 if next != nil && oldNext == nil {
109 func (e *CElement) setPrevAtomic(prev *CElement) {
111 oldPrev := atomic.LoadPointer(&e.prev)
112 if !atomic.CompareAndSwapPointer(&(e.prev), oldPrev, unsafe.Pointer(prev)) {
115 if prev == nil && oldPrev != nil { // We for-loop in PrevWait() so race is ok
118 if prev != nil && oldPrev == nil {
125 func (e *CElement) setRemovedAtomic() {
126 atomic.StoreUint32(&(e.removed), 1)
129 //--------------------------------------------------------------------------------
131 // CList represents a linked list.
132 // The zero value for CList is an empty list ready to use.
133 // Operations are goroutine-safe.
137 head *CElement // first element
138 tail *CElement // last element
139 len int // list length
142 func (l *CList) Init() *CList {
152 func New() *CList { return new(CList).Init() }
154 func (l *CList) Len() int {
160 func (l *CList) Front() *CElement {
166 func (l *CList) FrontWait() *CElement {
180 func (l *CList) Back() *CElement {
186 func (l *CList) BackWait() *CElement {
200 func (l *CList) PushBack(v interface{}) *CElement {
204 // Construct a new element
207 prevWg: waitGroup1(),
209 nextWg: waitGroup1(),
213 // Release waiters on FrontWait/BackWait maybe
224 l.tail.setNextAtomic(e)
225 e.setPrevAtomic(l.tail)
232 // CONTRACT: Caller must call e.DetachPrev() and/or e.DetachNext() to avoid memory leaks.
233 // NOTE: As per the contract of CList, removed elements cannot be added back.
234 func (l *CList) Remove(e *CElement) interface{} {
241 if l.head == nil || l.tail == nil {
242 panic("Remove(e) on empty CList")
244 if prev == nil && l.head != e {
245 panic("Remove(e) with false head")
247 if next == nil && l.tail != e {
248 panic("Remove(e) with false tail")
251 // If we're removing the only item, make CList FrontWait/BackWait wait.
257 // Connect next/prev and set head/tail
261 prev.setNextAtomic(next)
266 next.setPrevAtomic(prev)
269 // Set .Done() on e, otherwise waiters will wait forever.
281 func waitGroup1() (wg *sync.WaitGroup) {
282 wg = &sync.WaitGroup{}