| @ -1,155 +0,0 @@ | |||
| //! This module provides a facade for an abstract concurrent job executor. | |||
| //! | |||
| //! Mostly here to insulate the rest of this crate from the exact details of | |||
| //! the executor implementation, though it also owns the process-wide context | |||
| //! data structure against which handlers are run. | |||
| use std::sync::Arc; | |||
| use threadpool; | |||
| use crate::context::Context; | |||
| /// Default number of threads spawned by Executor instances | |||
| const NUM_THREADS: usize = 8; | |||
| /// The trait of objects that can be run by an Executor. | |||
| pub trait Job: Send { | |||
| /// Runs this job against the given context. | |||
| fn execute(self: Box<Self>, context: &Context); | |||
| } | |||
| /// A concurrent job execution engine. | |||
| pub struct Executor { | |||
| /// The context against which jobs are executed. | |||
| context: Arc<Context>, | |||
| /// Executes the jobs. | |||
| pool: threadpool::ThreadPool, | |||
| } | |||
| impl Executor { | |||
| /// Builds a new executor against the given context. | |||
| pub fn new(context: Context) -> Self { | |||
| Self { | |||
| context: Arc::new(context), | |||
| pool: threadpool::Builder::new() | |||
| .num_threads(NUM_THREADS) | |||
| .thread_name("Executor".to_string()) | |||
| .build(), | |||
| } | |||
| } | |||
| /// Schedules execution of the given job on this executor. | |||
| pub fn schedule(&self, job: Box<dyn Job>) { | |||
| let context = self.context.clone(); | |||
| self.pool.execute(move || job.execute(&*context)); | |||
| } | |||
| /// Blocks until all scheduled jobs are executed, then returns the context. | |||
| pub fn join(self) -> Context { | |||
| self.pool.join(); | |||
| // The only copies of the Arc are passed to the closures executed on | |||
| // the threadpool. Once the pool is join()ed, there cannot exist any | |||
| // other copies than ours, so we are safe to unwrap() the Arc. | |||
| Arc::try_unwrap(self.context).unwrap() | |||
| } | |||
| } | |||
| #[cfg(test)] | |||
| mod tests { | |||
| use std::sync::{Arc, Barrier}; | |||
| use solstice_proto::{User, UserStatus}; | |||
| use crate::context::{Context, ContextBundle}; | |||
| use super::{Executor, Job}; | |||
| #[test] | |||
| fn immediate_join_returns_unchanged_context() { | |||
| let bundle = ContextBundle::default(); | |||
| let context = Executor::new(bundle.context).join(); | |||
| assert_eq!(context.state.lock().users.get_list(), vec![]); | |||
| assert_eq!(context.state.lock().rooms.get_room_list(), vec![]); | |||
| } | |||
| struct Waiter { | |||
| barrier: Arc<Barrier>, | |||
| } | |||
| impl Job for Waiter { | |||
| fn execute(self: Box<Self>, _context: &Context) { | |||
| self.barrier.wait(); | |||
| } | |||
| } | |||
| #[test] | |||
| fn join_waits_for_all_jobs() { | |||
| let bundle = ContextBundle::default(); | |||
| let executor = Executor::new(bundle.context); | |||
| let barrier = Arc::new(Barrier::new(2)); | |||
| executor.schedule(Box::new(Waiter { | |||
| barrier: barrier.clone(), | |||
| })); | |||
| executor.schedule(Box::new(Waiter { | |||
| barrier: barrier.clone(), | |||
| })); | |||
| executor.join(); | |||
| } | |||
| struct UserAdder { | |||
| pub user: User, | |||
| } | |||
| impl Job for UserAdder { | |||
| fn execute(self: Box<Self>, context: &Context) { | |||
| context.state.lock().users.insert(self.user); | |||
| } | |||
| } | |||
| #[test] | |||
| fn jobs_access_context() { | |||
| let bundle = ContextBundle::default(); | |||
| let executor = Executor::new(bundle.context); | |||
| let user1 = User { | |||
| name: "potato".to_string(), | |||
| status: UserStatus::Offline, | |||
| average_speed: 0, | |||
| num_downloads: 0, | |||
| unknown: 0, | |||
| num_files: 0, | |||
| num_folders: 0, | |||
| num_free_slots: 0, | |||
| country: "YO".to_string(), | |||
| }; | |||
| let mut user2 = user1.clone(); | |||
| user2.name = "rutabaga".to_string(); | |||
| executor.schedule(Box::new(UserAdder { | |||
| user: user1.clone(), | |||
| })); | |||
| executor.schedule(Box::new(UserAdder { | |||
| user: user2.clone(), | |||
| })); | |||
| let context = executor.join(); | |||
| let expected_users = | |||
| vec![(user1.name.clone(), user1), (user2.name.clone(), user2)]; | |||
| let mut users = context.state.lock().users.get_list(); | |||
| users.sort(); | |||
| assert_eq!(users, expected_users); | |||
| } | |||
| } | |||