rust-lang / rust-lang/libs-team

Add LocalWaker support

Open
#191 46 comments 22 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

api-change-proposal WG-async
Dominant language
Rust
Stars
178
Forks
28
Avg merge
15m
Merged PRs (30d)
1

Description

Proposal

Problem statement

The current implementation of Waker is Send + Sync, which means that it can be freely moved between threads. While this is necessary for work stealing, it also imposes a performance penalty on single threaded and thread per core (TPC) runtimes. This runtime cost is contrary to Rust's zero cost abstraction philosophy. Furthermore, this missing API has led some async runtimes to implement unsound wakers, both by neglect and intentionally.

Motivation, use-cases

Local wakers leverage Rust's compile-time thread safety to avoid unnecessary thread synchronization, offering a compile-time guarantee that a waker has not been moved to another thread. This allows async runtimes to specialize their waker implementations and skip unnecessary atomic operations, thereby improving performance.

It is noteworthy to mention that while this is especially useful for TPC runtimes, work stealing runtimes may also benefit from performing this specialization. So this should not be considered a niche use case.

Solution sketches

Construction

Constructing a local waker is done in the same way as a shared waker. You just use the from_raw function, which takes a RawWaker.

use std::task::LocalWaker; 
let waker = unsafe { LocalWaker::from_raw(raw_waker) }

Alternatively, the LocalWake trait may be used analogously to Wake.

use std::task::LocalWake;

thread_local! {
    /// A queue containing all woken tasks
    static WOKEN_TASKS: RefCell<VecDeque<Task>>; 
}

struct Task(Box<dyn Future<Output = ()>>);

impl LocalWake for Task {
    fn wake(self: Rc<Self>) {
        WOKEN_TASKS.with(|woken_tasks| {
            woken_tasks.borrow_mut().push_back(self)
        })
    }
}

The safety requirements for constructing a LocalWaker from a RawWaker would be the same as a Waker, except for thread safety.

Usage

A local waker can be accessed with the local_waker method on Context and woken just like a regular waker. All contexts will return a valid local_waker, regardless of whether they are specialized for this case or not.

let local_waker: &LocalWaker = cx.local_waker(); 
local_waker.wake(); 
ContextBuilder

In order to construct a specialized Context, the ConstextBuilder type must be used. This type may be extended in the future to allow for more functionality for Context.

use std::task::{Context, LocalWaker, Waker, ContextBuilder}; 

let waker: Waker = /* ... */; 
let local_waker: LocalWaker = /* ... */;

let context = ContextBuilder::new()
    .waker(&waker)
    .local_waker(&local_waker)
    .build()
    .expect("at least one waker must be set"); 

Then it can be accessed with the local_waker method on Context.

let local_waker: &LocalWaker = cx.local_waker(); 

If a LocalWaker is not set on a context, this one would still return a valid LocalWaker, because a local waker can be trivially constructed from the context's waker.

If a runtime does not intend to support thread safe wakers, they should not provide a Waker to ContextBuilder, and it will construct a Context that panics in the call to waker().

Links and related work

What happens now?

This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked IRLO discussion and API change proposal process, then compare the proposed LocalWaker, LocalWake, and ContextBuilder APIs with the existing Waker, Wake, Context, and RawWaker entry points. Done means the API design is resolved by the libs-api team and the accepted behavior and validation are agreed.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.