Module std::sync

Waiting between tasks, over the executor's keys (std::task::Tasks's waker, park and wake): a task that waits is parked in its executor's table, never polling, and the task that wakes it readies it there. Both work under every executor (std::task::Pool, std::net's Evented and Blocking, std::sim's worlds). Under weir explore of a searched world every park is a scheduling point, and so is the start of every wait (Tasks.preempt()), where a wake-up from another thread can land between a check and the wait that depends on it.

Source: std/sync.weir; committed interface: std/sync.iface. Index

struct Notify

pub struct Notify {
    /* private fields */
}

A wake-up one task sends another, as tokio's Notify: notified() waits for a notification, notify_one() wakes the oldest waiting task or, with none waiting, keeps one notification for the next notified(), which then returns at once, so a notification that comes before the wait is not lost (at most one is kept); notify_all() wakes every task waiting now and keeps none. A task cancelled while it waits takes no notification with it, but its place in the queue stays, and the notify_one() that reaches it wakes no one.

impl Notify

method new

pub fn new() -> Notify with Alloc

A Notify with no task waiting and no notification kept; allocates its wait queue.

Committed interface (std/sync.iface):

bound Alloc: Alloc any
aborts: Alloc.alloc

method notified

pub fn notified(&self) with Tasks, Lock portable, Alloc portable

Waits for a notification: the kept one if there is one, else the next notify_one() or notify_all(). Parks the task (Tasks); Lock guards the queue, Alloc grows it. It begins with Tasks.preempt(), the point where another thread's notify_all() could land between the caller's check and its wait, so a searched world tries that (systems.md §21.17).

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
bound Alloc: Alloc any portable
migrates: Tasks.wake migrates inline | Tasks.waker migrates inline
aborts: Tasks.park, Tasks.preempt, Alloc.alloc
scheduling point: always
holds self across a migration: Tasks resumes elsewhere | Tasks.waker migrates inline
hands a closure entering Alloc to Lock
reads regions _

method notify_one

pub fn notify_one(&self) with Tasks, Lock portable

Wakes the oldest waiting task, or keeps the notification for the next notified(). Never waits.

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
migrates: Tasks.wake migrates inline
scheduling point: Lock not quiet | Tasks not quiet
reads regions _

method notify_all

pub fn notify_all(&self) with Tasks, Lock portable

Wakes every task waiting now; keeps no notification. Never waits.

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
migrates: Tasks.wake migrates inline
scheduling point: Lock not quiet | Tasks not quiet
holds self across a migration: Tasks.wake migrates inline
reads regions _

struct Watch

pub struct Watch<T> {
    /* private fields */
}

A value tasks watch for changes, as tokio's watch channel: Watch::send stores a new value and wakes every task waiting in Watch::changed, and Watch::get answers the value now. Each value sent has a version, one past the one before (the first value's is 0). A watcher keeps the version it last saw in a usize of its own, seen, and changed(&mut seen) waits until the version differs from it, so a send that lands between the watcher's look and its wait is not lost: the wait queues the task's key before it looks at the version. A check followed by Notify::notified() misses a notify_all() in between, which keeps no notification. A watcher sees the latest value, not every one: two sends before it looks are one change to it. send wakes only the tasks waiting when it is called, so a watcher that was woken and waits again waits for the next send.

impl<T: copy + owned + portable> Watch<T>

method new

pub fn new(v: T) -> Watch<T> with Alloc

A watch holding v at version 0; allocates the wait queue.

Committed interface (std/sync.iface):

bound Alloc: Alloc any
aborts: Alloc.alloc

method get

pub fn get(&self) -> T with Lock

The value now.

Committed interface (std/sync.iface):

bound Lock: Lock any
scheduling point: Lock not quiet

method read

pub fn read(&self, seen: &mut usize) -> T with Lock

The value now, with its version stored in seen: what a watcher reads before it waits in Watch::changed.

Committed interface (std/sync.iface):

bound Lock: Lock any
scheduling point: Lock not quiet
writes through seen: everything and its shape

method send

pub fn send(&self, v: T) with Tasks, Lock portable

Stores v as the next version and wakes every task waiting in Watch::changed. Never waits.

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
migrates: Tasks migrates inline | Tasks.wake migrates inline
scheduling point: always
holds self across a migration: Tasks.wake migrates inline
reads regions _

method send_if_modified

pub fn send_if_modified(&self, f: fn(&mut T) -> Bool env {A}) -> Bool with
    Tasks,
    Lock portable,

Changes the value in place with f under the watch's lock: when f answers true the result is the next version and every task waiting in Watch::changed is woken, as send does, and when it answers false nothing changes and none is woken (tokio's send_if_modified). One call is one step, so |x| if *x < n { *x = n; true } else { false } keeps a maximum however many tasks call it at once. Answers what f answered.

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
migrates: Tasks migrates inline | Tasks.wake migrates inline
scheduling point: always
holds what may not move at a scheduling point: always
holds self across a migration: Tasks.wake migrates inline
callback f: envelope {A}; handed to Lock; holds self; runs under a lock
reads regions _

method changed

pub fn changed(&self, seen: &mut usize) with Tasks, Lock portable, Alloc portable

Waits until the version differs from seen, then stores the version now in seen; returns at once when it already differs. Parks the task (Tasks); Lock guards the value and the wait queue, Alloc grows the queue.

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
bound Alloc: Alloc any portable
may diverge
migrates: Tasks.wake migrates inline | Tasks.waker migrates inline
aborts: Tasks.park, Alloc.alloc
scheduling point: always
holds self across a migration: Tasks resumes elsewhere | Tasks.wake migrates inline | Tasks.waker migrates inline
holds seen across a migration: Tasks resumes elsewhere | Tasks.wake migrates inline | Tasks.waker migrates inline
writes through seen: everything
hands a closure entering Alloc to Lock
reads regions _

struct Channel

pub struct Channel<T> {
    /* private fields */
}

A bounded multi-producer, multi-consumer channel, as Go's buffered channels or Rust's std::sync::mpsc::sync_channel with any number of receivers: one value, lent to the tasks that send and receive, which may run on any of an executor's threads (T is portable). send waits while the channel holds cap values and recv while it holds none, each parked until the other side makes room or sends. close() ends it: a send from then on hands its value back in Err, and recv answers what is still buffered, then None. Values arrive in the order their senders claimed their places, each received exactly once.

impl<T: portable> Channel<T>

method new

pub fn new(cap: usize) -> Channel<T> with Alloc {alloc: tail} requires 0usize < cap

A channel that holds up to cap values; requires cap > 0: there is no rendezvous channel, so Channel::new(0) is rejected where it is written (R-EntryFact). Allocates the ring.

Committed interface (std/sync.iface):

bound Alloc: Alloc { alloc: tail } any

method send

pub fn send(&self, v: T) -> Result<Unit, T> with Tasks, Lock portable, Alloc portable

Sends v, parking while the channel is full; Err(v) once it is closed, including for a sender parked when close comes.

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
bound Alloc: Alloc any portable
may diverge
migrates: Tasks migrates inline | Tasks.wake migrates inline | Tasks.waker migrates inline
aborts: Tasks.park, Tasks.preempt, Alloc.alloc
scheduling point: always
holds self across a migration: Tasks migrates inline | Tasks resumes elsewhere | Tasks.wake migrates inline | Tasks.waker migrates inline
holds v across a migration: Tasks migrates inline | Tasks resumes elsewhere | Tasks.wake migrates inline | Tasks.waker migrates inline
destroys values of T
stores T in regions _
hands a closure entering Alloc to Lock
reads regions _

method recv

pub fn recv(&self) -> Option<T> with Tasks, Lock portable, Alloc portable

The oldest value sent, parking while the channel is empty; None once it is closed and every value sent has been received (a parked receiver is woken by close).

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
bound Alloc: Alloc any portable
may diverge
migrates: Tasks migrates inline | Tasks.wake migrates inline | Tasks.waker migrates inline
aborts: Tasks.park, Tasks.preempt, Alloc.alloc
scheduling point: always
holds self across a migration: Tasks migrates inline | Tasks resumes elsewhere | Tasks.wake migrates inline | Tasks.waker migrates inline
destroys values of T
hands a closure entering Alloc to Lock
reads regions _

method close

pub fn close(&self) with Tasks, Lock portable

Closes the channel and wakes every task waiting on it; values already sent stay to be received. Closing twice is closing once. Never waits.

Committed interface (std/sync.iface):

bound Tasks: Tasks portable cross
bound Lock: Lock any portable
migrates: Tasks migrates inline | Tasks.wake migrates inline
scheduling point: always
holds self across a migration: Tasks migrates inline | Tasks resumes elsewhere | Tasks.wake migrates inline
reads regions _