use crate::awake::Reservation;
use crate::error::Refused;
use crate::exec::{Exec, TeamAuth};
use crate::policy::{WaitOverride, WaitPolicy};
use crate::pool::Pool;
use core::fmt;
use core::num::NonZeroUsize;
use core::time::Duration;
use std::sync::{Condvar, Mutex, PoisonError};
use std::thread::ThreadId;
struct Gate {
owner: Mutex<Option<ThreadId>>,
free: Condvar,
}
pub(crate) struct LeaderGuard<'t>(&'t Gate);
impl Drop for LeaderGuard<'_> {
fn drop(&mut self) {
*self.0.owner.lock().unwrap_or_else(PoisonError::into_inner) = None;
self.0.free.notify_one();
}
}
pub struct Team {
reservation: Reservation,
policy: WaitPolicy,
gate: Gate,
pool: Pool,
}
pub struct IntoTeamError {
pool: Pool,
reason: Refused,
}
impl IntoTeamError {
pub fn reason(&self) -> Refused {
self.reason
}
pub fn into_pool(self) -> Pool {
self.pool
}
}
impl fmt::Debug for IntoTeamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("IntoTeamError")
.field("reason", &self.reason)
.finish_non_exhaustive()
}
}
impl fmt::Display for IntoTeamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "cannot turn the pool into a team: {}", self.reason)
}
}
impl std::error::Error for IntoTeamError {}
impl Pool {
pub fn into_team(self, width: usize) -> Result<Team, IntoTeamError> {
self.into_team_with(width, WaitOverride::new())
}
pub fn into_team_with(self, width: usize, over: WaitOverride) -> Result<Team, IntoTeamError> {
let width = width.clamp(1, self.width().get());
let policy = self.wait_policy().with(over);
let release = WaitPolicy::new(Duration::ZERO, false);
let reservation = match self
.awake
.acquire(&self, width - 1, release, policy.idle_spin())
{
Ok(r) => r,
Err(reason) => return Err(IntoTeamError { pool: self, reason }),
};
let team = Team {
reservation,
policy,
gate: Gate {
owner: Mutex::new(None),
free: Condvar::new(),
},
pool: self,
};
if width > 1 {
let mut token = team.exec(width);
let _ = token.team(|mut t| t.acknowledge());
}
Ok(team)
}
}
impl Team {
pub fn pool(&self) -> &Pool {
&self.pool
}
pub fn into_pool(self) -> Pool {
let Team {
reservation, pool, ..
} = self;
drop(reservation);
pool
}
pub fn size(&self) -> NonZeroUsize {
NonZeroUsize::new(self.reservation.ids.len() + 1).unwrap_or(NonZeroUsize::MIN)
}
pub fn exec(&self, budget: usize) -> Exec<'_> {
let me = std::thread::current().id();
let mut owner = self
.gate
.owner
.lock()
.unwrap_or_else(PoisonError::into_inner);
loop {
match *owner {
None => break,
Some(id) if id == me => {
panic!(
"Team::exec called by the thread that already holds a token of this team"
)
}
Some(_) => {
owner = self
.gate
.free
.wait(owner)
.unwrap_or_else(PoisonError::into_inner);
}
}
}
*owner = Some(me);
drop(owner);
self.token(budget)
}
pub fn try_exec(&self, budget: usize) -> Result<Exec<'_>, Refused> {
let mut owner = self
.gate
.owner
.lock()
.unwrap_or_else(PoisonError::into_inner);
if owner.is_some() {
return Err(Refused::Busy);
}
*owner = Some(std::thread::current().id());
drop(owner);
Ok(self.token(budget))
}
fn token(&self, budget: usize) -> Exec<'_> {
let budget = budget.clamp(1, self.size().get());
let auth = TeamAuth {
awake: &self.pool.awake,
ids: &self.reservation.ids[..budget - 1],
policy: self.policy,
};
Exec::from_team(&self.pool, auth, LeaderGuard(&self.gate))
}
}
impl fmt::Debug for Team {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Team").field("size", &self.size()).finish()
}
}