use crate::io::IoWorkerConfig;
use crate::utils::SpinLock;
use crate::BUG_MESSAGE;
use std::mem::discriminant;
#[allow(clippy::struct_excessive_bools, reason = "False positive")]
struct ConfigStats {
number_of_executors_with_enabled_io_worker_and_work_sharing: usize,
number_of_executors_with_enabled_thread_pool_and_work_sharing: usize,
number_of_executors_with_work_sharing_and_without_io_worker: usize,
number_of_executors_with_work_sharing_and_without_thread_pool: usize,
}
impl ConfigStats {
const fn new() -> Self {
Self {
number_of_executors_with_enabled_io_worker_and_work_sharing: 0,
number_of_executors_with_enabled_thread_pool_and_work_sharing: 0,
number_of_executors_with_work_sharing_and_without_io_worker: 0,
number_of_executors_with_work_sharing_and_without_thread_pool: 0,
}
}
}
static GLOBAL_CONFIG_STATS: SpinLock<ConfigStats> = SpinLock::new(ConfigStats::new());
pub const DEFAULT_BUF_CAP: u32 = 4096;
#[derive(Clone)]
pub(crate) struct ValidConfig {
pub(crate) buffer_cap: u32,
pub(crate) io_worker_config: Option<IoWorkerConfig>,
pub(crate) number_of_thread_workers: usize,
pub(crate) work_sharing_level: usize,
}
impl ValidConfig {
pub const fn is_work_sharing_enabled(&self) -> bool {
self.work_sharing_level != usize::MAX
}
pub const fn is_thread_pool_enabled(&self) -> bool {
self.number_of_thread_workers != 0
}
}
impl Drop for ValidConfig {
fn drop(&mut self) {
if self.work_sharing_level != usize::MAX {
let mut guard = Some(GLOBAL_CONFIG_STATS.lock());
let shared_config_stats = guard.as_mut().expect(BUG_MESSAGE);
if self.io_worker_config.is_some() {
shared_config_stats.number_of_executors_with_enabled_io_worker_and_work_sharing -=
1;
} else {
shared_config_stats.number_of_executors_with_work_sharing_and_without_io_worker -=
1;
}
if self.is_thread_pool_enabled() {
shared_config_stats
.number_of_executors_with_enabled_thread_pool_and_work_sharing -= 1;
} else {
shared_config_stats
.number_of_executors_with_work_sharing_and_without_thread_pool -= 1;
}
}
}
}
#[derive(Clone, Copy)]
pub struct Config {
buffer_cap: u32,
io_worker_config: Option<IoWorkerConfig>,
number_of_thread_workers: usize,
work_sharing_level: usize,
}
const AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_IO_WORKER: &str = "\
An attempt to create an Executor with work sharing and with an \
IO worker has failed because another Executor was created with \
work sharing enabled and without an IO worker enabled. \
This is unacceptable because an Executor who does not have an \
IO worker cannot take on a task that requires an IO worker.";
const AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_WITHOUT_IO_WORKER: &str = "\
An attempt to create an Executor with work sharing and without an \
IO worker has failed because another Executor was created with \
an IO worker and work sharing enabled. \
This is unacceptable because an Executor who does not have an \
IO worker cannot take on a task that requires an IO worker.";
const AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_THREAD_POOL: &str = "\
An attempt to create an Executor with work sharing and with a \
thread pool enabled has failed because another Executor was created with \
work sharing enabled and without a thread pool enabled. \
This is unacceptable because an Executor who does not have a \
thread pool cannot take on a task that requires a thread pool.";
const AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_WITHOUT_THREAD_POOL: &str = "\
An attempt to create an Executor with work sharing and without a \
thread pool enabled has failed because another Executor was created with \
both a thread pool and work sharing enabled. \
This is unacceptable because an Executor who does not have a \
thread pool cannot take on a task that requires a thread pool.";
impl Config {
pub const fn default() -> Self {
Self {
buffer_cap: DEFAULT_BUF_CAP,
io_worker_config: Some(IoWorkerConfig::default()),
number_of_thread_workers: 1,
work_sharing_level: 7,
}
}
pub const fn buffer_cap(&self) -> u32 {
self.buffer_cap
}
#[must_use]
pub const fn set_buffer_cap(mut self, buf_cap: u32) -> Self {
self.buffer_cap = buf_cap;
self
}
pub const fn io_worker_config(&self) -> Option<IoWorkerConfig> {
self.io_worker_config
}
pub const fn set_io_worker_config(
mut self,
io_worker_config: Option<IoWorkerConfig>,
) -> Result<Self, &'static str> {
match io_worker_config {
Some(io_worker_config) => {
if let Err(err) = io_worker_config.validate() {
return Err(err);
}
self.io_worker_config = Some(io_worker_config);
}
None => {
self.io_worker_config = None;
}
}
Ok(self)
}
#[must_use]
pub const fn disable_io_worker(mut self) -> Self {
self.io_worker_config = None;
self
}
pub const fn number_of_thread_workers(&self) -> usize {
self.number_of_thread_workers
}
pub const fn is_thread_pool_enabled(&self) -> bool {
self.number_of_thread_workers != 0
}
#[must_use]
pub const fn set_numbers_of_thread_workers(mut self, number_of_thread_workers: usize) -> Self {
self.number_of_thread_workers = number_of_thread_workers;
self
}
pub const fn is_work_sharing_enabled(&self) -> bool {
self.work_sharing_level != usize::MAX
}
#[must_use]
pub const fn enable_work_sharing(mut self) -> Self {
if self.work_sharing_level == usize::MAX {
self.work_sharing_level = 7;
}
self
}
#[must_use]
pub const fn disable_work_sharing(mut self) -> Self {
self.work_sharing_level = usize::MAX;
self
}
#[must_use]
pub const fn set_work_sharing_level(mut self, work_sharing_level: usize) -> Self {
if work_sharing_level == 0 {
self.work_sharing_level = 1;
} else {
self.work_sharing_level = work_sharing_level;
}
self
}
#[must_use]
pub(crate) fn validate(self) -> ValidConfig {
if self.work_sharing_level != usize::MAX {
let mut shared_config_stats = GLOBAL_CONFIG_STATS.lock();
if self.io_worker_config.is_some() {
if shared_config_stats.number_of_executors_with_work_sharing_and_without_io_worker
!= 0
{
panic!("{AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_IO_WORKER}");
}
shared_config_stats.number_of_executors_with_enabled_io_worker_and_work_sharing +=
1;
} else {
if shared_config_stats.number_of_executors_with_enabled_io_worker_and_work_sharing
!= 0
{
panic!(
"{AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_WITHOUT_IO_WORKER}"
);
}
shared_config_stats.number_of_executors_with_work_sharing_and_without_io_worker +=
1;
}
if self.is_thread_pool_enabled() {
if shared_config_stats.number_of_executors_with_work_sharing_and_without_thread_pool
!= 0
{
panic!("{AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_THREAD_POOL}");
}
shared_config_stats
.number_of_executors_with_enabled_thread_pool_and_work_sharing += 1;
} else {
if shared_config_stats.number_of_executors_with_enabled_thread_pool_and_work_sharing
!= 0
{
panic!(
"{AN_ATTEMPT_TO_CREATE_EXECUTOR_WITH_WORK_SHARING_AND_WITHOUT_THREAD_POOL}"
);
}
shared_config_stats
.number_of_executors_with_work_sharing_and_without_thread_pool += 1;
}
}
ValidConfig {
buffer_cap: self.buffer_cap,
io_worker_config: self.io_worker_config,
number_of_thread_workers: self.number_of_thread_workers,
work_sharing_level: self.work_sharing_level,
}
}
}
impl From<&ValidConfig> for Config {
fn from(config: &ValidConfig) -> Self {
Self {
buffer_cap: config.buffer_cap,
io_worker_config: config.io_worker_config,
number_of_thread_workers: config.number_of_thread_workers,
work_sharing_level: config.work_sharing_level,
}
}
}
impl PartialEq for Config {
fn eq(&self, other: &Self) -> bool {
self.buffer_cap == other.buffer_cap
&& discriminant(&self.io_worker_config) == discriminant(&other.io_worker_config)
&& self.number_of_thread_workers == other.number_of_thread_workers
&& self.work_sharing_level == other.work_sharing_level
}
}
impl Eq for Config {}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate as orengine;
use std::panic;
use std::sync::atomic;
use std::sync::{Condvar as STDCvar, Mutex as STDMutex};
const NUMBER_OF_TESTS: usize = 6;
pub(crate) static WAS_READY: (STDMutex<bool>, STDCvar) = (STDMutex::new(false), STDCvar::new());
static NUMBER_OF_READY_TESTS: atomic::AtomicUsize = atomic::AtomicUsize::new(0);
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn handle_test_ready() {
let prev = NUMBER_OF_READY_TESTS.fetch_add(1, atomic::Ordering::SeqCst);
if prev == NUMBER_OF_TESTS - 1 {
*WAS_READY.0.lock().unwrap() = true;
WAS_READY.1.notify_all();
}
assert!(prev < NUMBER_OF_TESTS, "{}", BUG_MESSAGE);
}
fn get_lock() -> std::sync::MutexGuard<'static, ()> {
LOCK.lock().unwrap_or_else(|e| {
LOCK.clear_poison();
e.into_inner()
})
}
#[orengine::test::test_local]
fn test_default_config() {
let lock = get_lock();
let config = Config::default().validate();
assert_eq!(config.buffer_cap, DEFAULT_BUF_CAP);
assert!(config.io_worker_config.is_some());
assert!(config.is_thread_pool_enabled());
assert_ne!(config.work_sharing_level, usize::MAX);
drop(lock);
handle_test_ready();
}
#[orengine::test::test_local]
fn test_config() {
let lock = get_lock();
let config = Config::default()
.set_buffer_cap(1024)
.set_io_worker_config(None)
.unwrap()
.set_numbers_of_thread_workers(0)
.disable_work_sharing();
let config = config.validate();
assert_eq!(config.buffer_cap, 1024);
assert!(config.io_worker_config.is_none());
assert!(!config.is_thread_pool_enabled());
assert_eq!(config.work_sharing_level, usize::MAX);
assert!(!config.is_work_sharing_enabled());
drop(lock);
handle_test_ready();
}
fn handle_panic_in_config_test(func: impl FnOnce() + panic::UnwindSafe) {
let lock = get_lock();
let res = panic::catch_unwind(func);
handle_test_ready();
drop(lock);
if let Err(err) = res {
panic::resume_unwind(err);
} else {
panic!("test failed");
}
}
#[orengine::test::test_local]
#[allow(
clippy::should_panic_without_expect,
reason = "panic message is too long"
)]
#[should_panic]
fn test_config_first_case_panic() {
handle_panic_in_config_test(|| {
let _first_config = Config::default().validate();
let _second_config = Config::default()
.set_io_worker_config(None)
.unwrap()
.enable_work_sharing()
.validate();
});
}
#[orengine::test::test_local]
#[allow(
clippy::should_panic_without_expect,
reason = "panic message is too long"
)]
#[should_panic]
fn test_config_second_case_panic() {
handle_panic_in_config_test(|| {
let _first_config = Config::default()
.set_io_worker_config(None)
.unwrap()
.enable_work_sharing()
.validate();
let _second_config = Config::default().validate();
});
}
#[orengine::test::test_local]
#[allow(
clippy::should_panic_without_expect,
reason = "panic message is too long"
)]
#[should_panic]
fn test_config_third_case_panic() {
handle_panic_in_config_test(|| {
let _first_config = Config::default()
.set_numbers_of_thread_workers(0)
.enable_work_sharing()
.validate();
let _second_config = Config::default().validate();
});
}
#[orengine::test::test_local]
#[allow(
clippy::should_panic_without_expect,
reason = "panic message is too long"
)]
#[should_panic]
fn test_config_fourth_case_panic() {
handle_panic_in_config_test(|| {
let _first_config = Config::default().validate();
let _second_config = Config::default()
.set_numbers_of_thread_workers(0)
.enable_work_sharing()
.validate();
});
}
}