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;