Hone logo
Hone
Problems

Implementing a Basic Waker System in Rust

This challenge involves creating a fundamental component of Rust's asynchronous programming: a waker system. You'll implement a simplified version of std::task::Waker and the associated mechanisms for waking up tasks that are waiting for an operation to complete. This is crucial for understanding how asynchronous futures are driven to completion.

Problem Description

Your task is to implement a basic waker system. This system will allow an asynchronous task (represented by a Future) to signal that it is ready to make progress. The system should consist of a Waker that can be cloned and used to wake up a corresponding "task". You'll also need a way to associate a Waker with a particular task and to manage the state of tasks that are pending.

Key Requirements:

  1. Waker Structure: Create a Waker struct. This Waker should be clonable.
  2. wake Method: The Waker must have a wake method. Calling wake on a Waker instance should trigger a notification that the associated task is ready.
  3. wake_by_ref Method: Implement a wake_by_ref method that behaves similarly to wake but takes a reference to the Waker.
  4. Task Executor (Simplified): Create a simple mechanism (e.g., a struct) that can:
    • Store tasks that are waiting to be polled.
    • Provide a Waker to a task when it's polled.
    • Receive notifications when a Waker is called.
    • Schedule a task for polling when its Waker is invoked.
  5. Integration with Future (Conceptual): While you won't implement a full Future trait, your system should conceptually work with a Future that might yield Poll::Pending.

Expected Behavior:

When a Future polls and returns Poll::Pending, it will be given a Waker. The Future should store this Waker. When the event the Future was waiting for occurs, it should call wake() on its stored Waker. This wake-up signal should eventually lead to the Future being polled again by the executor.

Edge Cases:

  • Multiple Wakes: A task might be woken up multiple times before it's polled. The system should handle this gracefully.
  • Cloning Wakers: The Waker can be cloned. Ensure that waking one clone wakes the original task.
  • No Outstanding Wakes: The executor should not attempt to poll tasks that haven't been woken up.

Examples

Example 1: Basic Waking

Let's imagine a simple scenario where a task needs to be woken up once.

  • Scenario:

    1. An executor is set up.
    2. A task is registered with the executor.
    3. The task is polled and returns Poll::Pending, receiving a Waker. It stores this Waker.
    4. Later, the Waker is used to call wake().
    5. The executor detects the wake-up and schedules the task for re-polling.
  • Conceptual Output (for demonstration purposes, actual execution would involve polling logic): The executor receives a wake-up notification for "Task A". It then queues "Task A" for its next polling cycle.

Example 2: Multiple Wakers for the Same Task

A task might receive multiple Waker instances (e.g., from different threads or asynchronous operations).

  • Scenario:

    1. A task is polled, receives Waker_X and stores it.
    2. The task is polled again, receives Waker_Y (which refers to the same underlying task) and stores it (or the executor handles this).
    3. Waker_X.wake() is called.
    4. Waker_Y.wake() is called.
    5. The executor should only schedule the task for polling once, despite multiple wake-up signals.
  • Conceptual Output: The executor receives wake-up notifications for "Task A". Even though there were two distinct Waker instances, the executor schedules "Task A" for polling only once.

Constraints

  • Waker Cloning: The Waker must implement Clone.
  • Send and Sync: For a practical async system, Waker and the underlying task notification mechanism should ideally be Send and Sync to allow for multi-threaded executors. You should aim for this.
  • No External Crates: Do not use external asynchronous runtime crates like tokio, async-std, or futures. Use only the Rust standard library, specifically std::task.
  • Simplified Executor: The executor's polling mechanism can be a simple loop or manual invocation. You don't need to implement a full scheduler that runs on a thread pool.
  • Arc Usage: You will likely need to use std::sync::Arc to share the wake-up mechanism across multiple Waker clones.

Notes

  • std::task::RawWaker and std::task::RawWakerVTable: The standard library's Waker is built upon RawWaker and RawWakerVTable. You will need to understand and implement these to create your own Waker.
  • Arc for Shared State: When cloning a Waker, the underlying mechanism that signals the task completion needs to be shared. Arc is a good candidate for this.
  • Task State: You'll need a way to represent the state of a task (e.g., whether it's pending, ready to poll).
  • Focus on the Waking Mechanism: The primary goal is to correctly implement the Waker and its interaction with a conceptual executor. The Future itself can be a placeholder or a very simple struct that always returns Poll::Pending until it's woken.
  • Success: Success is demonstrated by being able to create a Waker, clone it, wake the associated task, and have a mechanism that can detect this wake-up and signal that the task is ready to be polled again.
Loading editor...
rust