Skip to main content

tenferro_cpu/
resource_domain.rs

1use std::num::NonZeroUsize;
2use std::sync::Arc;
3
4use thiserror::Error;
5
6use crate::{
7    CpuDomainExecutor, CpuDomainExecutorCapabilities, CpuDomainId, CpuPlacementGuarantee, CpuSet,
8    ResolvedCpuPlacement,
9};
10
11/// Ownership class of a CPU resource domain.
12///
13/// # Examples
14///
15/// ```rust
16/// use tenferro_cpu::CpuDomainOwnership;
17///
18/// assert_ne!(
19///     CpuDomainOwnership::Managed,
20///     CpuDomainOwnership::ExternalManaged,
21/// );
22/// ```
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum CpuDomainOwnership {
25    /// Tenferro constructed and owns the resource domain.
26    Managed,
27    /// The application supplied and owns the executor resource policy.
28    ExternalManaged,
29}
30
31/// Typed failure to construct an externally managed CPU resource domain.
32///
33/// # Examples
34///
35/// ```rust
36/// use tenferro_cpu::ExternalCpuDomainError;
37///
38/// let error = ExternalCpuDomainError::ThreadBudgetExceedsWorkerCount {
39///     thread_budget: 4,
40///     worker_count: 2,
41/// };
42/// assert!(error.to_string().contains("4"));
43/// ```
44#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
45pub enum ExternalCpuDomainError {
46    /// The resolved placement contains no logical CPUs.
47    #[error("external CPU domain placement must contain at least one CPU")]
48    EmptyPlacementCpuSet,
49    /// The executor reported no workers.
50    #[error("external CPU domain executor must report at least one worker")]
51    ZeroExecutorWorkers,
52    /// The requested thread budget is larger than the executor worker count.
53    #[error(
54        "external CPU domain thread budget {thread_budget} exceeds executor worker count {worker_count}"
55    )]
56    ThreadBudgetExceedsWorkerCount {
57        /// Requested maximum number of participating threads.
58        thread_budget: usize,
59        /// Workers reported by the supplied executor.
60        worker_count: usize,
61    },
62}
63
64#[derive(Debug)]
65pub(crate) struct CpuResourceDomain {
66    id: CpuDomainId,
67    placement: ResolvedCpuPlacement,
68    executor: Arc<dyn CpuDomainExecutor>,
69    thread_budget: NonZeroUsize,
70    placement_guarantee: CpuPlacementGuarantee,
71    ownership: CpuDomainOwnership,
72}
73
74impl CpuResourceDomain {
75    pub(crate) fn new(
76        id: CpuDomainId,
77        placement: ResolvedCpuPlacement,
78        executor: Arc<dyn CpuDomainExecutor>,
79        thread_budget: NonZeroUsize,
80        placement_guarantee: CpuPlacementGuarantee,
81        ownership: CpuDomainOwnership,
82    ) -> Self {
83        Self {
84            id,
85            placement,
86            executor,
87            thread_budget,
88            placement_guarantee,
89            ownership,
90        }
91    }
92
93    pub(crate) fn id(&self) -> CpuDomainId {
94        self.id
95    }
96
97    pub(crate) fn placement(&self) -> &ResolvedCpuPlacement {
98        &self.placement
99    }
100
101    pub(crate) fn cpus(&self) -> &CpuSet {
102        self.placement.cpus()
103    }
104
105    pub(crate) fn executor(&self) -> &Arc<dyn CpuDomainExecutor> {
106        &self.executor
107    }
108
109    pub(crate) fn thread_budget(&self) -> NonZeroUsize {
110        self.thread_budget
111    }
112
113    pub(crate) fn placement_guarantee(&self) -> CpuPlacementGuarantee {
114        self.placement_guarantee
115    }
116
117    pub(crate) fn ownership(&self) -> CpuDomainOwnership {
118        self.ownership
119    }
120
121    pub(crate) fn executor_capabilities(&self) -> CpuDomainExecutorCapabilities {
122        self.executor().capabilities()
123    }
124}
125
126/// Caller-supplied descriptor for one externally managed CPU resource domain.
127///
128/// The descriptor retains the supplied executor without replacing its pool or
129/// changing its affinity claim. Registration and process-CPU-set validation
130/// are performed later by [`crate::CpuBackend`].
131///
132/// # Examples
133///
134/// ```rust
135/// use std::num::NonZeroUsize;
136/// use std::sync::Arc;
137/// use tenferro_cpu::{
138///     CpuContext, CpuDomainOwnership, CpuId, CpuPlacementGuarantee, CpuSet,
139///     ExternalCpuDomain, ResolvedCpuPlacement,
140/// };
141/// use tenferro_tensor::CpuDomainId;
142///
143/// let domain = ExternalCpuDomain::new(
144///     CpuDomainId::new(7),
145///     ResolvedCpuPlacement::AllAllowed {
146///         cpus: CpuSet::new([CpuId::new(0)])?,
147///     },
148///     Arc::new(CpuContext::with_threads(1)?),
149///     NonZeroUsize::new(1).unwrap(),
150///     CpuPlacementGuarantee::AdvisoryDeclared,
151/// )?;
152/// assert_eq!(domain.ownership(), CpuDomainOwnership::ExternalManaged);
153/// # Ok::<(), Box<dyn std::error::Error>>(())
154/// ```
155#[derive(Debug)]
156pub struct ExternalCpuDomain {
157    domain: CpuResourceDomain,
158}
159
160impl ExternalCpuDomain {
161    /// Construct one externally managed CPU resource-domain descriptor.
162    ///
163    /// The executor is retained for the complete descriptor lifetime. Exact
164    /// and advisory placement values remain caller declarations and do not
165    /// alter the executor's affinity capability.
166    ///
167    /// # Examples
168    ///
169    /// ```rust
170    /// use std::num::NonZeroUsize;
171    /// use std::sync::Arc;
172    /// use tenferro_cpu::{
173    ///     CpuContext, CpuId, CpuPlacementGuarantee, CpuSet, ExternalCpuDomain,
174    ///     ResolvedCpuPlacement,
175    /// };
176    /// use tenferro_tensor::CpuDomainId;
177    ///
178    /// let domain = ExternalCpuDomain::new(
179    ///     CpuDomainId::new(3),
180    ///     ResolvedCpuPlacement::AllAllowed {
181    ///         cpus: CpuSet::new([CpuId::new(0)])?,
182    ///     },
183    ///     Arc::new(CpuContext::with_threads(1)?),
184    ///     NonZeroUsize::new(1).unwrap(),
185    ///     CpuPlacementGuarantee::ExactDeclared,
186    /// )?;
187    /// assert_eq!(domain.id(), CpuDomainId::new(3));
188    /// # Ok::<(), Box<dyn std::error::Error>>(())
189    /// ```
190    ///
191    /// # Errors
192    ///
193    /// Returns [`ExternalCpuDomainError::EmptyPlacementCpuSet`] for an empty
194    /// resolved CPU set, [`ExternalCpuDomainError::ZeroExecutorWorkers`] when
195    /// the executor reports no workers, or
196    /// [`ExternalCpuDomainError::ThreadBudgetExceedsWorkerCount`] when
197    /// `thread_budget` is greater than the executor's worker count.
198    pub fn new(
199        id: CpuDomainId,
200        placement: ResolvedCpuPlacement,
201        executor: Arc<dyn CpuDomainExecutor>,
202        thread_budget: NonZeroUsize,
203        placement_guarantee: CpuPlacementGuarantee,
204    ) -> Result<Self, ExternalCpuDomainError> {
205        let worker_count = executor.capabilities().worker_count.get();
206        validate_external_domain_config(placement.cpus().len(), worker_count, thread_budget)?;
207        Ok(Self {
208            domain: CpuResourceDomain::new(
209                id,
210                placement,
211                executor,
212                thread_budget,
213                placement_guarantee,
214                CpuDomainOwnership::ExternalManaged,
215            ),
216        })
217    }
218
219    /// Return the caller-stable identity of this CPU domain.
220    ///
221    /// # Examples
222    ///
223    /// ```rust
224    /// use tenferro_cpu::ExternalCpuDomain;
225    /// use tenferro_tensor::CpuDomainId;
226    ///
227    /// let _id: fn(&ExternalCpuDomain) -> CpuDomainId = ExternalCpuDomain::id;
228    /// ```
229    pub fn id(&self) -> CpuDomainId {
230        self.domain.id()
231    }
232
233    /// Return the declared resolved placement.
234    ///
235    /// # Examples
236    ///
237    /// ```rust
238    /// use tenferro_cpu::{ExternalCpuDomain, ResolvedCpuPlacement};
239    ///
240    /// let _placement: fn(&ExternalCpuDomain) -> &ResolvedCpuPlacement =
241    ///     ExternalCpuDomain::placement;
242    /// ```
243    pub fn placement(&self) -> &ResolvedCpuPlacement {
244        self.domain.placement()
245    }
246
247    /// Return the logical CPUs declared for this domain.
248    ///
249    /// # Examples
250    ///
251    /// ```rust
252    /// use tenferro_cpu::{CpuSet, ExternalCpuDomain};
253    ///
254    /// let _cpus: fn(&ExternalCpuDomain) -> &CpuSet = ExternalCpuDomain::cpus;
255    /// ```
256    pub fn cpus(&self) -> &CpuSet {
257        self.domain.cpus()
258    }
259
260    /// Return the nonzero thread budget requested for tenferro work.
261    ///
262    /// # Examples
263    ///
264    /// ```rust
265    /// use std::num::NonZeroUsize;
266    /// use tenferro_cpu::ExternalCpuDomain;
267    ///
268    /// let _budget: fn(&ExternalCpuDomain) -> NonZeroUsize =
269    ///     ExternalCpuDomain::thread_budget;
270    /// ```
271    pub fn thread_budget(&self) -> NonZeroUsize {
272        self.domain.thread_budget()
273    }
274
275    /// Return whether placement is an exact or advisory declaration.
276    ///
277    /// # Examples
278    ///
279    /// ```rust
280    /// use tenferro_cpu::{CpuPlacementGuarantee, ExternalCpuDomain};
281    ///
282    /// let _guarantee: fn(&ExternalCpuDomain) -> CpuPlacementGuarantee =
283    ///     ExternalCpuDomain::placement_guarantee;
284    /// ```
285    pub fn placement_guarantee(&self) -> CpuPlacementGuarantee {
286        self.domain.placement_guarantee()
287    }
288
289    /// Return the external ownership diagnostic.
290    ///
291    /// # Examples
292    ///
293    /// ```rust
294    /// use tenferro_cpu::{CpuDomainOwnership, ExternalCpuDomain};
295    ///
296    /// let _ownership: fn(&ExternalCpuDomain) -> CpuDomainOwnership =
297    ///     ExternalCpuDomain::ownership;
298    /// ```
299    pub fn ownership(&self) -> CpuDomainOwnership {
300        self.domain.ownership()
301    }
302
303    /// Return the supplied executor's immutable capability descriptor.
304    ///
305    /// # Examples
306    ///
307    /// ```rust
308    /// use tenferro_cpu::{CpuDomainExecutorCapabilities, ExternalCpuDomain};
309    ///
310    /// let _capabilities: fn(&ExternalCpuDomain) -> CpuDomainExecutorCapabilities =
311    ///     ExternalCpuDomain::executor_capabilities;
312    /// ```
313    pub fn executor_capabilities(&self) -> CpuDomainExecutorCapabilities {
314        self.domain.executor_capabilities()
315    }
316}
317
318impl From<ExternalCpuDomain> for CpuResourceDomain {
319    fn from(domain: ExternalCpuDomain) -> Self {
320        domain.domain
321    }
322}
323
324fn validate_external_domain_config(
325    cpu_count: usize,
326    worker_count: usize,
327    thread_budget: NonZeroUsize,
328) -> Result<(), ExternalCpuDomainError> {
329    if cpu_count == 0 {
330        return Err(ExternalCpuDomainError::EmptyPlacementCpuSet);
331    }
332    if worker_count == 0 {
333        return Err(ExternalCpuDomainError::ZeroExecutorWorkers);
334    }
335    if thread_budget.get() > worker_count {
336        return Err(ExternalCpuDomainError::ThreadBudgetExceedsWorkerCount {
337            thread_budget: thread_budget.get(),
338            worker_count,
339        });
340    }
341    Ok(())
342}
343
344#[cfg(test)]
345mod tests;