Implement a Basic Task Scheduler in Rust
This challenge involves building a simple task scheduler in Rust. A task scheduler is a fundamental component in many operating systems and applications, responsible for managing and executing a set of tasks in an ordered fashion. This exercise will help you understand concurrency, thread management, and basic queueing mechanisms in Rust.
Problem Description
You need to implement a TaskScheduler struct that can accept tasks, store them, and execute them in the order they were received. A task is defined as a closure that takes no arguments and returns nothing (FnOnce()).
Key Requirements:
- Task Submission: The scheduler must have a method to accept tasks (closures) and add them to an internal queue.
- Task Execution: The scheduler must have a method to start processing tasks from the queue. This execution should happen in a separate thread to avoid blocking the main thread.
- Sequential Execution: Tasks must be executed in the exact order they were submitted (First-In, First-Out - FIFO).
- Thread Safety: Since tasks will be added from potentially multiple threads (though for this basic challenge, we'll focus on a single submission thread for simplicity), the internal queue needs to be thread-safe.
- Shutdown: The scheduler should provide a mechanism to gracefully shut down, ensuring all currently queued tasks are executed before the scheduler stops.
Expected Behavior:
When run() is called, a new thread will be spawned. This thread will continuously pull tasks from the queue and execute them one by one until a shutdown signal is received. When shutdown() is called, the scheduler should signal the worker thread to stop after completing the current task and all tasks already in the queue.
Edge Cases:
- Empty Queue: The scheduler should handle the case where
run()is called but no tasks have been submitted yet. - Submitting Tasks After Shutdown Initiated: Tasks submitted after
shutdown()has been called should ideally be rejected or ignored, or handled according to a defined policy (for this challenge, rejecting is sufficient). - No Tasks Submitted: If
shutdown()is called before any tasks are ever submitted, the scheduler should exit cleanly.
Examples
Example 1:
use std::thread;
use std::time::Duration;
// Assume TaskScheduler struct and its methods are implemented
let mut scheduler = TaskScheduler::new();
scheduler.submit(|| {
println!("Task 1 executed");
});
scheduler.submit(|| {
thread::sleep(Duration::from_millis(100)); // Simulate work
println!("Task 2 executed");
});
scheduler.submit(|| {
println!("Task 3 executed");
});
scheduler.run(); // Starts the worker thread
// Allow some time for tasks to be submitted and potentially started
thread::sleep(Duration::from_millis(50));
scheduler.shutdown(); // Signal for graceful shutdown
// The main thread might need to wait here for the worker to finish if not handled by scheduler itself.
// For this challenge, assume the shutdown method blocks or provides a way to join the thread.
/*
Expected Output (order of Task 1, 2, 3 is guaranteed, but interspersed with other potential output like 'Scheduler Shutting Down'):
Task 1 executed
Task 2 executed
Task 3 executed
Scheduler Shutting Down (or similar message from shutdown)
*/
Example 2:
use std::thread;
use std::time::Duration;
let mut scheduler = TaskScheduler::new();
scheduler.run(); // Start the scheduler with an empty queue
thread::sleep(Duration::from_millis(50)); // Give it a moment to potentially start
scheduler.submit(|| {
println!("This task should run.");
});
thread::sleep(Duration::from_millis(50)); // Allow task submission and execution
scheduler.shutdown();
/*
Expected Output:
This task should run.
Scheduler Shutting Down
*/
Example 3 (Task Submission after shutdown initiated):
use std::thread;
use std::time::Duration;
let mut scheduler = TaskScheduler::new();
scheduler.submit(|| {
println!("This task will run.");
});
scheduler.run();
thread::sleep(Duration::from_millis(10)); // Short sleep
scheduler.shutdown();
// Attempt to submit a task after shutdown
let submit_result = scheduler.submit(|| {
println!("This task should NOT run.");
});
// Depending on implementation, submit_result might be Ok(()) or an Err indicating scheduler is shutting down.
// For this problem, we expect this task *not* to be executed.
// Ensure shutdown completes.
thread::sleep(Duration::from_millis(200)); // Give enough time for the first task to run and scheduler to exit
/*
Expected Output:
This task will run.
Scheduler Shutting Down
*/
Constraints
- The
TaskSchedulermust be implemented usingstd::threadfor task execution. - The internal task queue must be thread-safe, consider using
std::sync::mpscfor communication orstd::sync::Mutexwithstd::collections::VecDeque. - The
submitmethod should accept closures that implementFnOnce() + Send + 'static. - The
runmethod should return immediately after spawning the worker thread. - The
shutdownmethod should block until the worker thread has finished executing all queued tasks and exited. - The scheduler should be able to handle at least 1000 tasks in its queue without significant performance degradation.
Notes
- Consider how to signal the worker thread to stop. A common pattern is to use a flag or a channel.
- Think about how to make the
submitmethod idempotent or to return an error if the scheduler is already shutting down. - The
FnOnce()trait bound is important because each closure will be executed only once. - The
'staticbound is necessary because the closure will be moved to another thread and must live for the entire duration of the program.