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  }