Minimal Tokio Runtime Implementation
This challenge asks you to implement a simplified version of Tokio's runtime, focusing on core concepts like task scheduling and asynchronous execution. Building a basic runtime helps understand how asynchronous Rust works under the hood and provides a foundation for more complex asynchronous systems. The goal is to create a runtime that can execute a few asynchronous tasks concurrently.
Problem Description
You are to implement a minimal Tokio-like runtime in Rust. This runtime should be able to:
- Manage Tasks: Maintain a list of asynchronous tasks (represented as
Futures). - Execute Tasks: Poll each task in a round-robin fashion until all tasks are completed. A task is considered complete when its
pollmethod returnsPoll::Ready. - Handle
PollResults: Correctly handle bothPoll::ReadyandPoll::Pendingresults from theFuture::pollmethod. If a task returnsPoll::Pending, it should be re-added to the task queue for later execution. - Simple Task Queue: Use a
Vecas a simple task queue. While not the most efficient, it's sufficient for this exercise. - No External Dependencies: The solution should not use the actual
tokiocrate. You are implementing a simplified version from scratch.
Key Requirements:
- The runtime must implement a
runmethod that takes a vector ofFutures as input. - The
runmethod should execute all provided futures concurrently. - The runtime should block until all futures have completed.
- The
Futuretrait must be implemented for a simpleCounterfuture (provided below).
Expected Behavior:
The runtime should execute the provided futures, polling them repeatedly until they all return Poll::Ready. The order of execution might vary slightly due to the round-robin scheduling, but all futures should eventually complete.
Edge Cases to Consider:
- What happens if a future never returns
Poll::Ready(an infinite loop)? While a full solution would handle this with timeouts, for this simplified version, you can assume all futures will eventually complete. - How does the runtime handle multiple futures returning
Poll::Pendingin a single iteration? They should be re-added to the queue.
Examples
Example 1:
Input: [Counter::new(3), Counter::new(5)]
Output: ((), ())
Explanation: Two counters are created, one completing after 3 polls, the other after 5. The runtime polls each counter until both are ready.
Example 2:
Input: [Counter::new(1), Counter::new(1)]
Output: ((), ())
Explanation: Two counters, each completing after 1 poll. The runtime polls each counter, and both complete quickly.
Example 3: (Edge Case - demonstrating re-queueing)
Input: [Counter::new(3), Counter::new(1)]
Output: ((), ())
Explanation: The first counter takes 3 polls, the second takes 1. The runtime polls the first, gets Pending, re-queues. Polls the second, gets Ready. Polls the first again, gets Ready.
Constraints
- The runtime should be implemented using only standard library features and the
Futuretrait. - The
runmethod should take aVec<Box<dyn Future + Send>>as input. TheSendbound is necessary for concurrent execution. - The runtime should not use any external crates (other than the standard library).
- Performance is not a primary concern for this exercise; clarity and correctness are more important.
- The
Counterfuture provided below should be used for testing.
Notes
- The
Futuretrait is defined as:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
- Consider using a
whileloop to continuously poll tasks until all are complete. - The
Contextpassed topollis not used in this simplified implementation, but it's required by theFuturetrait. - The
Sendtrait bound on the futures is important for allowing them to be moved between threads (although this simplified runtime doesn't use threads directly, it's good practice to include it). - The
Box<dyn Future + Send>allows you to store different types of futures in the same vector.
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
struct Counter {
count: i32,
}
impl Counter {
fn new(count: i32) -> Counter {
Counter { count }
}
}
impl Future for Counter {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.count > 0 {
self.count -= 1;
Poll::Pending
} else {
Poll::Ready(())
}
}
}
fn main() {}