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

     1  //go:build !scheduler.none
     2  
     3  package runtime
     4  
     5  import (
     6  	"internal/task"
     7  	"sync/atomic"
     8  	"unsafe"
     9  )
    10  
    11  // notifiedPlaceholder is a placeholder task which is used to indicate that the condition variable has been notified.
    12  var notifiedPlaceholder task.Task
    13  
    14  // Cond is a simplified condition variable, useful for notifying goroutines of interrupts.
    15  type Cond struct {
    16  	t *task.Task
    17  }
    18  
    19  // Notify sends a notification.
    20  // If the condition variable already has a pending notification, this returns false.
    21  func (c *Cond) Notify() bool {
    22  	for {
    23  		t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
    24  		switch t {
    25  		case nil:
    26  			// Nothing is waiting yet.
    27  			// Apply the notification placeholder.
    28  			if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), unsafe.Pointer(&notifiedPlaceholder)) {
    29  				return true
    30  			}
    31  		case &notifiedPlaceholder:
    32  			// The condition variable has already been notified.
    33  			return false
    34  		default:
    35  			// Unblock the waiting task.
    36  			if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
    37  				runqueuePushBack(t)
    38  				return true
    39  			}
    40  		}
    41  	}
    42  }
    43  
    44  // Poll checks for a notification.
    45  // If a notification is found, it is cleared and this returns true.
    46  func (c *Cond) Poll() bool {
    47  	for {
    48  		t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
    49  		switch t {
    50  		case nil:
    51  			// No notifications are present.
    52  			return false
    53  		case &notifiedPlaceholder:
    54  			// A notification arrived and there is no waiting goroutine.
    55  			// Clear the notification and return.
    56  			if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
    57  				return true
    58  			}
    59  		default:
    60  			// A task is blocked on the condition variable, which means it has not been notified.
    61  			return false
    62  		}
    63  	}
    64  }
    65  
    66  // Wait for a notification.
    67  // If the condition variable was previously notified, this returns immediately.
    68  func (c *Cond) Wait() {
    69  	cur := task.Current()
    70  	for {
    71  		t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t))))
    72  		switch t {
    73  		case nil:
    74  			// Condition variable has not been notified.
    75  			// Block the current task on the condition variable.
    76  			if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), nil, unsafe.Pointer(cur)) {
    77  				task.Pause()
    78  				return
    79  			}
    80  		case &notifiedPlaceholder:
    81  			// A notification arrived and there is no waiting goroutine.
    82  			// Clear the notification and return.
    83  			if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) {
    84  				return
    85  			}
    86  		default:
    87  			panic("interrupt.Cond: condition variable in use by another goroutine")
    88  		}
    89  	}
    90  }