github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/clist/clist.go (about) 1 package clist 2 3 /* 4 5 The purpose of CList is to provide a goroutine-safe linked-list. 6 This list can be traversed concurrently by any number of goroutines. 7 However, removed CElements cannot be added back. 8 NOTE: Not all methods of container/list are (yet) implemented. 9 NOTE: Removed elements need to DetachPrev or DetachNext consistently 10 to ensure garbage collection of removed elements. 11 12 */ 13 14 import ( 15 "fmt" 16 "sync" 17 ) 18 19 // MaxLength is the max allowed number of elements a linked list is 20 // allowed to contain. 21 // If more elements are pushed to the list it will panic. 22 const MaxLength = int(^uint(0) >> 1) 23 24 /* 25 CElement is an element of a linked-list 26 Traversal from a CElement is goroutine-safe. 27 28 We can't avoid using WaitGroups or for-loops given the documentation 29 spec without re-implementing the primitives that already exist in 30 golang/sync. Notice that WaitGroup allows many go-routines to be 31 simultaneously released, which is what we want. Mutex doesn't do 32 this. RWMutex does this, but it's clumsy to use in the way that a 33 WaitGroup would be used -- and we'd end up having two RWMutex's for 34 prev/next each, which is doubly confusing. 35 36 sync.Cond would be sort-of useful, but we don't need a write-lock in 37 the for-loop. Use sync.Cond when you need serial access to the 38 "condition". In our case our condition is if `next != nil || removed`, 39 and there's no reason to serialize that condition for goroutines 40 waiting on NextWait() (since it's just a read operation). 41 */ 42 type CElement struct { 43 mtx sync.RWMutex 44 prev *CElement 45 prevWg *sync.WaitGroup 46 prevWaitCh chan struct{} 47 next *CElement 48 nextWg *sync.WaitGroup 49 nextWaitCh chan struct{} 50 removed bool 51 52 Value interface{} // immutable 53 } 54 55 // Blocking implementation of Next(). 56 // May return nil iff CElement was tail and got removed. 57 func (e *CElement) NextWait() *CElement { 58 for { 59 e.mtx.RLock() 60 next := e.next 61 nextWg := e.nextWg 62 removed := e.removed 63 e.mtx.RUnlock() 64 65 if next != nil || removed { 66 return next 67 } 68 69 nextWg.Wait() 70 // e.next doesn't necessarily exist here. 71 // That's why we need to continue a for-loop. 72 } 73 } 74 75 // Blocking implementation of Prev(). 76 // May return nil iff CElement was head and got removed. 77 func (e *CElement) PrevWait() *CElement { 78 for { 79 e.mtx.RLock() 80 prev := e.prev 81 prevWg := e.prevWg 82 removed := e.removed 83 e.mtx.RUnlock() 84 85 if prev != nil || removed { 86 return prev 87 } 88 89 prevWg.Wait() 90 } 91 } 92 93 // PrevWaitChan can be used to wait until Prev becomes not nil. Once it does, 94 // channel will be closed. 95 func (e *CElement) PrevWaitChan() <-chan struct{} { 96 e.mtx.RLock() 97 defer e.mtx.RUnlock() 98 99 return e.prevWaitCh 100 } 101 102 // NextWaitChan can be used to wait until Next becomes not nil. Once it does, 103 // channel will be closed. 104 func (e *CElement) NextWaitChan() <-chan struct{} { 105 e.mtx.RLock() 106 defer e.mtx.RUnlock() 107 108 return e.nextWaitCh 109 } 110 111 // Nonblocking, may return nil if at the end. 112 func (e *CElement) Next() *CElement { 113 e.mtx.RLock() 114 val := e.next 115 e.mtx.RUnlock() 116 return val 117 } 118 119 // Nonblocking, may return nil if at the end. 120 func (e *CElement) Prev() *CElement { 121 e.mtx.RLock() 122 prev := e.prev 123 e.mtx.RUnlock() 124 return prev 125 } 126 127 func (e *CElement) Removed() bool { 128 e.mtx.RLock() 129 isRemoved := e.removed 130 e.mtx.RUnlock() 131 return isRemoved 132 } 133 134 func (e *CElement) DetachNext() { 135 e.mtx.Lock() 136 if !e.removed { 137 e.mtx.Unlock() 138 panic("DetachNext() must be called after Remove(e)") 139 } 140 e.next = nil 141 e.mtx.Unlock() 142 } 143 144 func (e *CElement) DetachPrev() { 145 e.mtx.Lock() 146 if !e.removed { 147 e.mtx.Unlock() 148 panic("DetachPrev() must be called after Remove(e)") 149 } 150 e.prev = nil 151 e.mtx.Unlock() 152 } 153 154 // NOTE: This function needs to be safe for 155 // concurrent goroutines waiting on nextWg. 156 func (e *CElement) SetNext(newNext *CElement) { 157 e.mtx.Lock() 158 159 oldNext := e.next 160 e.next = newNext 161 if oldNext != nil && newNext == nil { 162 // See https://golang.org/pkg/sync/: 163 // 164 // If a WaitGroup is reused to wait for several independent sets of 165 // events, new Add calls must happen after all previous Wait calls have 166 // returned. 167 e.nextWg = waitGroup1() // WaitGroups are difficult to re-use. 168 e.nextWaitCh = make(chan struct{}) 169 } 170 if oldNext == nil && newNext != nil { 171 e.nextWg.Done() 172 close(e.nextWaitCh) 173 } 174 e.mtx.Unlock() 175 } 176 177 // NOTE: This function needs to be safe for 178 // concurrent goroutines waiting on prevWg 179 func (e *CElement) SetPrev(newPrev *CElement) { 180 e.mtx.Lock() 181 182 oldPrev := e.prev 183 e.prev = newPrev 184 if oldPrev != nil && newPrev == nil { 185 e.prevWg = waitGroup1() // WaitGroups are difficult to re-use. 186 e.prevWaitCh = make(chan struct{}) 187 } 188 if oldPrev == nil && newPrev != nil { 189 e.prevWg.Done() 190 close(e.prevWaitCh) 191 } 192 e.mtx.Unlock() 193 } 194 195 func (e *CElement) SetRemoved() { 196 e.mtx.Lock() 197 198 e.removed = true 199 200 // This wakes up anyone waiting in either direction. 201 if e.prev == nil { 202 e.prevWg.Done() 203 close(e.prevWaitCh) 204 } 205 if e.next == nil { 206 e.nextWg.Done() 207 close(e.nextWaitCh) 208 } 209 e.mtx.Unlock() 210 } 211 212 // -------------------------------------------------------------------------------- 213 214 // CList represents a linked list. 215 // The zero value for CList is an empty list ready to use. 216 // Operations are goroutine-safe. 217 // Panics if length grows beyond the max. 218 type CList struct { 219 mtx sync.RWMutex 220 wg *sync.WaitGroup 221 waitCh chan struct{} 222 head *CElement // first element 223 tail *CElement // last element 224 len int // list length 225 maxLen int // max list length 226 } 227 228 func (l *CList) Init() *CList { 229 l.mtx.Lock() 230 231 l.wg = waitGroup1() 232 l.waitCh = make(chan struct{}) 233 l.head = nil 234 l.tail = nil 235 l.len = 0 236 l.mtx.Unlock() 237 return l 238 } 239 240 // Return CList with MaxLength. CList will panic if it goes beyond MaxLength. 241 func New() *CList { return newWithMax(MaxLength) } 242 243 // Return CList with given maxLength. 244 // Will panic if list exceeds given maxLength. 245 func newWithMax(maxLength int) *CList { 246 l := new(CList) 247 l.maxLen = maxLength 248 return l.Init() 249 } 250 251 func (l *CList) Len() int { 252 l.mtx.RLock() 253 defer l.mtx.RUnlock() 254 255 return l.len 256 } 257 258 func (l *CList) Front() *CElement { 259 l.mtx.RLock() 260 head := l.head 261 l.mtx.RUnlock() 262 return head 263 } 264 265 func (l *CList) FrontWait() *CElement { 266 // Loop until the head is non-nil else wait and try again 267 for { 268 l.mtx.RLock() 269 head := l.head 270 wg := l.wg 271 l.mtx.RUnlock() 272 273 if head != nil { 274 return head 275 } 276 wg.Wait() 277 // NOTE: If you think l.head exists here, think harder. 278 } 279 } 280 281 func (l *CList) Back() *CElement { 282 l.mtx.RLock() 283 back := l.tail 284 l.mtx.RUnlock() 285 return back 286 } 287 288 func (l *CList) BackWait() *CElement { 289 for { 290 l.mtx.RLock() 291 tail := l.tail 292 wg := l.wg 293 l.mtx.RUnlock() 294 295 if tail != nil { 296 return tail 297 } 298 wg.Wait() 299 // l.tail doesn't necessarily exist here. 300 // That's why we need to continue a for-loop. 301 } 302 } 303 304 // WaitChan can be used to wait until Front or Back becomes not nil. Once it 305 // does, channel will be closed. 306 func (l *CList) WaitChan() <-chan struct{} { 307 l.mtx.Lock() 308 defer l.mtx.Unlock() 309 310 return l.waitCh 311 } 312 313 // Panics if list grows beyond its max length. 314 func (l *CList) PushBack(v interface{}) *CElement { 315 l.mtx.Lock() 316 317 // Construct a new element 318 e := &CElement{ 319 prev: nil, 320 prevWg: waitGroup1(), 321 prevWaitCh: make(chan struct{}), 322 next: nil, 323 nextWg: waitGroup1(), 324 nextWaitCh: make(chan struct{}), 325 removed: false, 326 Value: v, 327 } 328 329 // Release waiters on FrontWait/BackWait maybe 330 if l.len == 0 { 331 l.wg.Done() 332 close(l.waitCh) 333 } 334 if l.len >= l.maxLen { 335 panic(fmt.Sprintf("clist: maximum length list reached %d", l.maxLen)) 336 } 337 l.len++ 338 339 // Modify the tail 340 if l.tail == nil { 341 l.head = e 342 l.tail = e 343 } else { 344 e.SetPrev(l.tail) // We must init e first. 345 l.tail.SetNext(e) // This will make e accessible. 346 l.tail = e // Update the list. 347 } 348 l.mtx.Unlock() 349 return e 350 } 351 352 // CONTRACT: Caller must call e.DetachPrev() and/or e.DetachNext() to avoid memory leaks. 353 // NOTE: As per the contract of CList, removed elements cannot be added back. 354 func (l *CList) Remove(e *CElement) interface{} { 355 l.mtx.Lock() 356 357 prev := e.Prev() 358 next := e.Next() 359 360 if l.head == nil || l.tail == nil { 361 l.mtx.Unlock() 362 panic("Remove(e) on empty CList") 363 } 364 if prev == nil && l.head != e { 365 l.mtx.Unlock() 366 panic("Remove(e) with false head") 367 } 368 if next == nil && l.tail != e { 369 l.mtx.Unlock() 370 panic("Remove(e) with false tail") 371 } 372 373 // If we're removing the only item, make CList FrontWait/BackWait wait. 374 if l.len == 1 { 375 l.wg = waitGroup1() // WaitGroups are difficult to re-use. 376 l.waitCh = make(chan struct{}) 377 } 378 379 // Update l.len 380 l.len-- 381 382 // Connect next/prev and set head/tail 383 if prev == nil { 384 l.head = next 385 } else { 386 prev.SetNext(next) 387 } 388 if next == nil { 389 l.tail = prev 390 } else { 391 next.SetPrev(prev) 392 } 393 394 // Set .Done() on e, otherwise waiters will wait forever. 395 e.SetRemoved() 396 397 l.mtx.Unlock() 398 return e.Value 399 } 400 401 func waitGroup1() (wg *sync.WaitGroup) { 402 wg = &sync.WaitGroup{} 403 wg.Add(1) 404 return 405 }