github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/sync/cond.go (about)

     1  package sync
     2  
     3  import "internal/task"
     4  
     5  type Cond struct {
     6  	L Locker
     7  
     8  	unlocking *earlySignal
     9  	blocked   task.Stack
    10  }
    11  
    12  // earlySignal is a type used to implement a stack for signalling waiters while they are unlocking.
    13  type earlySignal struct {
    14  	next *earlySignal
    15  
    16  	signaled bool
    17  }
    18  
    19  func NewCond(l Locker) *Cond {
    20  	return &Cond{L: l}
    21  }
    22  
    23  func (c *Cond) trySignal() bool {
    24  	// Pop a blocked task off of the stack, and schedule it if applicable.
    25  	t := c.blocked.Pop()
    26  	if t != nil {
    27  		scheduleTask(t)
    28  		return true
    29  	}
    30  
    31  	// If there any tasks which are currently unlocking, signal one.
    32  	if c.unlocking != nil {
    33  		c.unlocking.signaled = true
    34  		c.unlocking = c.unlocking.next
    35  		return true
    36  	}
    37  
    38  	// There was nothing to signal.
    39  	return false
    40  }
    41  
    42  func (c *Cond) Signal() {
    43  	c.trySignal()
    44  }
    45  
    46  func (c *Cond) Broadcast() {
    47  	// Signal everything.
    48  	for c.trySignal() {
    49  	}
    50  }
    51  
    52  func (c *Cond) Wait() {
    53  	// Add an earlySignal frame to the stack so we can be signalled while unlocking.
    54  	early := earlySignal{
    55  		next: c.unlocking,
    56  	}
    57  	c.unlocking = &early
    58  
    59  	// Temporarily unlock L.
    60  	c.L.Unlock()
    61  
    62  	// Re-acquire the lock before returning.
    63  	defer c.L.Lock()
    64  
    65  	// If we were signaled while unlocking, immediately complete.
    66  	if early.signaled {
    67  		return
    68  	}
    69  
    70  	// Remove the earlySignal frame.
    71  	prev := c.unlocking
    72  	for prev != nil && prev.next != &early {
    73  		prev = prev.next
    74  	}
    75  	if prev != nil {
    76  		prev.next = early.next
    77  	} else {
    78  		c.unlocking = early.next
    79  	}
    80  
    81  	// Wait for a signal.
    82  	c.blocked.Push(task.Current())
    83  	task.Pause()
    84  }