We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 20 of 25
Fairness and starvation in your scheduler
Change ReadyQueue::push so the supplied executor supports both FIFO and
LIFO scheduling. Keep pop removing from the front, and leave the supplied
futures, waker, executor and schedule function in place.
pub fn schedule(counts: Vec<u32>, policy: String) -> Vec<usize>
Task i appends its index to a shared log counts[i] times. After each entry
it awaits the supplied yield_now() future, which queues that task again and
returns Pending once. Return the resulting log.
Queue rules
| Policy | Where push adds an ID | Where pop removes an ID |
|---|---|---|
"fifo" |
Back | Front |
"lifo" |
Front | Front |
The same push operation is used when spawning tasks and when waking them. Tasks are spawned in increasing index order. Therefore FIFO starts with task 0, while LIFO starts with the highest index, even before any task wakes itself.
"lifo" is the only string that enables LIFO in the supplied schedule.
Every other string uses FIFO; do not add case folding or an error response.
Expected order
schedule([3, 2, 1], "fifo") = [0, 1, 2, 0, 1, 0]
schedule([3, 2, 1], "lifo") = [2, 1, 1, 0, 0, 0]
schedule([0, 2, 0], "fifo") = [1, 1]
schedule([], "lifo") = []
For FIFO, visit the unfinished tasks in increasing index order on each round,
recording one entry per task that still has work. For LIFO, the highest-index
nonempty task supplies all its entries first, then the next lower index, and
so on. This happens because a task that yields is pushed straight back to the
front. Both policies return exactly sum(counts) log entries.
A zero-count task still gets polled and completes, but adds no entry. Every other task also needs a final poll after its last logged yield to complete. Thus log entries and executor polls are different counts.
Input size and the executor guard
For this exercise, sum(counts) + counts.len() is at most 99,999, calculated
without integer overflow. Each supplied task takes counts[i] + 1 polls to
complete. The executor's 100,000-iteration guard also needs one iteration to
notice that the queue is empty.
Do not change that guard. Inputs exceeding this bound can hit its panic even though each task has finite work. In particular, using all 100,000 iterations to poll tasks leaves no iteration for the empty-queue check.
What this demonstrates about fairness
FIFO distributes turns among the runnable tasks in this example. LIFO favors the task that was queued most recently. Neither policy changes the work each task must eventually perform within the input bound, but the order is visible in the log.
Now imagine a task that repeatedly wakes itself and returns Pending forever.
Under this pure LIFO policy, once it reaches the front, it keeps putting itself
back there. Other queued tasks receive no further polls. That is starvation.
The supplied executor would eventually stop at its guard; an executor without
that guard could keep favoring the same task indefinitely.
FIFO prevents that particular queue-order problem, because a self-waking task returns to the back. It does not guarantee equal CPU time. A task can spend much longer inside one poll than another task does, or fail to return at all. This executor cannot poll another task until the current poll returns. Cooperative scheduling therefore needs both queue policy and tasks that return control.
Why a scheduler might favor recent work
Running a recently woken task can improve locality when it consumes data another task just produced. The benefit depends on the workload, processor and scheduler; it is not a guaranteed speedup, and task-local register values are not generally shared across tasks.
Some production schedulers combine a ready queue with a preferred slot for recently woken work, plus limits on how long that slot can dominate. Tokio's multi-thread scheduler documents one such design. This exercise models only the two simple queue policies; it does not reproduce a production runtime's fairness guarantees, priorities, work stealing or wake deduplication.
Your solution must compile, match the output order and pass Clippy with warnings treated as errors.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.