You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
7.0 KiB

  1. package clist
  2. /*
  3. The purpose of CList is to provide a goroutine-safe linked-list.
  4. This list can be traversed concurrently by any number of goroutines.
  5. However, removed CElements cannot be added back.
  6. NOTE: Not all methods of container/list are (yet) implemented.
  7. NOTE: Removed elements need to DetachPrev or DetachNext consistently
  8. to ensure garbage collection of removed elements.
  9. */
  10. import (
  11. "fmt"
  12. "sync"
  13. )
  14. // MaxLength is the max allowed number of elements a linked list is
  15. // allowed to contain.
  16. // If more elements are pushed to the list it will panic.
  17. const MaxLength = int(^uint(0) >> 1)
  18. /*
  19. CElement is an element of a linked-list
  20. Traversal from a CElement is goroutine-safe.
  21. We can't avoid using WaitGroups or for-loops given the documentation
  22. spec without re-implementing the primitives that already exist in
  23. golang/sync. Notice that WaitGroup allows many go-routines to be
  24. simultaneously released, which is what we want. Mutex doesn't do
  25. this. RWMutex does this, but it's clumsy to use in the way that a
  26. WaitGroup would be used -- and we'd end up having two RWMutex's for
  27. prev/next each, which is doubly confusing.
  28. sync.Cond would be sort-of useful, but we don't need a write-lock in
  29. the for-loop. Use sync.Cond when you need serial access to the
  30. "condition". In our case our condition is if `next != nil || removed`,
  31. and there's no reason to serialize that condition for goroutines
  32. waiting on NextWait() (since it's just a read operation).
  33. */
  34. type CElement struct {
  35. mtx sync.RWMutex
  36. prev *CElement
  37. next *CElement
  38. nextWaitCh chan struct{}
  39. removed bool
  40. Value interface{} // immutable
  41. }
  42. // Blocking implementation of Next().
  43. // May return nil iff CElement was tail and got removed.
  44. func (e *CElement) NextWait() *CElement {
  45. for {
  46. e.mtx.RLock()
  47. next := e.next
  48. removed := e.removed
  49. signal := e.nextWaitCh
  50. e.mtx.RUnlock()
  51. if next != nil || removed {
  52. return next
  53. }
  54. <-signal
  55. // e.next doesn't necessarily exist here.
  56. // That's why we need to continue a for-loop.
  57. }
  58. }
  59. // NextWaitChan can be used to wait until Next becomes not nil. Once it does,
  60. // channel will be closed.
  61. func (e *CElement) NextWaitChan() <-chan struct{} {
  62. e.mtx.RLock()
  63. defer e.mtx.RUnlock()
  64. return e.nextWaitCh
  65. }
  66. // Nonblocking, may return nil if at the end.
  67. func (e *CElement) Next() *CElement {
  68. e.mtx.RLock()
  69. val := e.next
  70. e.mtx.RUnlock()
  71. return val
  72. }
  73. // Nonblocking, may return nil if at the end.
  74. func (e *CElement) Prev() *CElement {
  75. e.mtx.RLock()
  76. prev := e.prev
  77. e.mtx.RUnlock()
  78. return prev
  79. }
  80. func (e *CElement) Removed() bool {
  81. e.mtx.RLock()
  82. isRemoved := e.removed
  83. e.mtx.RUnlock()
  84. return isRemoved
  85. }
  86. func (e *CElement) detachNext() {
  87. e.mtx.Lock()
  88. if !e.removed {
  89. e.mtx.Unlock()
  90. panic("DetachNext() must be called after Remove(e)")
  91. }
  92. e.next = nil
  93. e.mtx.Unlock()
  94. }
  95. func (e *CElement) DetachPrev() {
  96. e.mtx.Lock()
  97. if !e.removed {
  98. e.mtx.Unlock()
  99. panic("DetachPrev() must be called after Remove(e)")
  100. }
  101. e.prev = nil
  102. e.mtx.Unlock()
  103. }
  104. // NOTE: This function needs to be safe for
  105. // concurrent goroutines waiting on nextWg.
  106. func (e *CElement) setNext(newNext *CElement) {
  107. e.mtx.Lock()
  108. oldNext := e.next
  109. e.next = newNext
  110. if oldNext != nil && newNext == nil {
  111. // See https://golang.org/pkg/sync/:
  112. //
  113. // If a WaitGroup is reused to wait for several independent sets of
  114. // events, new Add calls must happen after all previous Wait calls have
  115. // returned.
  116. e.nextWaitCh = make(chan struct{})
  117. }
  118. if oldNext == nil && newNext != nil {
  119. close(e.nextWaitCh)
  120. }
  121. e.mtx.Unlock()
  122. }
  123. // NOTE: This function needs to be safe for
  124. // concurrent goroutines waiting on prevWg
  125. func (e *CElement) setPrev(newPrev *CElement) {
  126. e.mtx.Lock()
  127. defer e.mtx.Unlock()
  128. e.prev = newPrev
  129. }
  130. func (e *CElement) setRemoved() {
  131. e.mtx.Lock()
  132. defer e.mtx.Unlock()
  133. e.removed = true
  134. // This wakes up anyone waiting.
  135. if e.next == nil {
  136. close(e.nextWaitCh)
  137. }
  138. }
  139. //--------------------------------------------------------------------------------
  140. // CList represents a linked list.
  141. // The zero value for CList is an empty list ready to use.
  142. // Operations are goroutine-safe.
  143. // Panics if length grows beyond the max.
  144. type CList struct {
  145. mtx sync.RWMutex
  146. waitCh chan struct{}
  147. head *CElement // first element
  148. tail *CElement // last element
  149. len int // list length
  150. maxLen int // max list length
  151. }
  152. // Return CList with MaxLength. CList will panic if it goes beyond MaxLength.
  153. func New() *CList { return newWithMax(MaxLength) }
  154. // Return CList with given maxLength.
  155. // Will panic if list exceeds given maxLength.
  156. func newWithMax(maxLength int) *CList {
  157. l := new(CList)
  158. l.maxLen = maxLength
  159. l.waitCh = make(chan struct{})
  160. l.head = nil
  161. l.tail = nil
  162. l.len = 0
  163. return l
  164. }
  165. func (l *CList) Len() int {
  166. l.mtx.RLock()
  167. len := l.len
  168. l.mtx.RUnlock()
  169. return len
  170. }
  171. func (l *CList) Front() *CElement {
  172. l.mtx.RLock()
  173. head := l.head
  174. l.mtx.RUnlock()
  175. return head
  176. }
  177. func (l *CList) frontWait() *CElement {
  178. // Loop until the head is non-nil else wait and try again
  179. for {
  180. l.mtx.RLock()
  181. head := l.head
  182. signal := l.waitCh
  183. l.mtx.RUnlock()
  184. if head != nil {
  185. return head
  186. }
  187. <-signal
  188. // NOTE: If you think l.head exists here, think harder.
  189. }
  190. }
  191. func (l *CList) Back() *CElement {
  192. l.mtx.RLock()
  193. back := l.tail
  194. l.mtx.RUnlock()
  195. return back
  196. }
  197. // WaitChan can be used to wait until Front or Back becomes not nil. Once it
  198. // does, channel will be closed.
  199. func (l *CList) WaitChan() <-chan struct{} {
  200. l.mtx.Lock()
  201. defer l.mtx.Unlock()
  202. return l.waitCh
  203. }
  204. // Panics if list grows beyond its max length.
  205. func (l *CList) PushBack(v interface{}) *CElement {
  206. l.mtx.Lock()
  207. // Construct a new element
  208. e := &CElement{
  209. prev: nil,
  210. next: nil,
  211. nextWaitCh: make(chan struct{}),
  212. removed: false,
  213. Value: v,
  214. }
  215. // Release waiters on FrontWait/BackWait maybe
  216. if l.len == 0 {
  217. close(l.waitCh)
  218. }
  219. if l.len >= l.maxLen {
  220. panic(fmt.Sprintf("clist: maximum length list reached %d", l.maxLen))
  221. }
  222. l.len++
  223. // Modify the tail
  224. if l.tail == nil {
  225. l.head = e
  226. l.tail = e
  227. } else {
  228. e.setPrev(l.tail) // We must init e first.
  229. l.tail.setNext(e) // This will make e accessible.
  230. l.tail = e // Update the list.
  231. }
  232. l.mtx.Unlock()
  233. return e
  234. }
  235. // CONTRACT: Caller must call e.DetachPrev() and/or e.DetachNext() to avoid memory leaks.
  236. // NOTE: As per the contract of CList, removed elements cannot be added back.
  237. func (l *CList) Remove(e *CElement) interface{} {
  238. l.mtx.Lock()
  239. defer l.mtx.Unlock()
  240. prev := e.Prev()
  241. next := e.Next()
  242. if l.head == nil || l.tail == nil {
  243. panic("Remove(e) on empty CList")
  244. }
  245. if prev == nil && l.head != e {
  246. panic("Remove(e) with false head")
  247. }
  248. if next == nil && l.tail != e {
  249. panic("Remove(e) with false tail")
  250. }
  251. // If we're removing the only item, make CList FrontWait/BackWait wait.
  252. if l.len == 1 {
  253. l.waitCh = make(chan struct{})
  254. }
  255. // Update l.len
  256. l.len--
  257. // Connect next/prev and set head/tail
  258. if prev == nil {
  259. l.head = next
  260. } else {
  261. prev.setNext(next)
  262. }
  263. if next == nil {
  264. l.tail = prev
  265. } else {
  266. next.setPrev(prev)
  267. }
  268. // Set .Done() on e, otherwise waiters will wait forever.
  269. e.setRemoved()
  270. return e.Value
  271. }