1use super::{
2 invalid_resource, AdmissionFitPolicy, AdmissionPressureAction, Digest, DynamicResourceShape,
3 ExecutionFrameId, NodeId, RequestAuthorityId, ResourceWorkShape, SequenceAuthorityId,
4 Serialize, Sha256, TokenSpanWork, VNextError,
5};
6use crate::vnext::ReusableExecutionBucketId;
7use std::{ops::Range, sync::Arc};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct StepResourceAdmissionRequest {
11 pub(super) work_shape: Arc<BatchWorkShape>,
12 pub(super) fit_policy: AdmissionFitPolicy,
13 pub(super) pressure_action: AdmissionPressureAction,
14 pub(super) reusable_execution_bucket_id: Option<ReusableExecutionBucketId>,
15}
16
17impl StepResourceAdmissionRequest {
18 pub fn new(
19 work_shape: impl Into<Arc<BatchWorkShape>>,
20 fit_policy: AdmissionFitPolicy,
21 pressure_action: AdmissionPressureAction,
22 ) -> Result<Self, VNextError> {
23 Ok(Self {
24 work_shape: work_shape.into(),
25 fit_policy,
26 pressure_action,
27 reusable_execution_bucket_id: None,
28 })
29 }
30
31 pub fn with_reusable_execution_bucket(mut self, bucket_id: ReusableExecutionBucketId) -> Self {
32 self.reusable_execution_bucket_id = Some(bucket_id);
33 self
34 }
35
36 pub fn work_shape(&self) -> &BatchWorkShape {
37 &self.work_shape
38 }
39
40 pub(crate) fn immediate_shape(&self) -> DynamicResourceShape {
41 self.work_shape.immediate_shape()
42 }
43
44 pub(crate) fn fit_shape(&self) -> DynamicResourceShape {
45 match self.fit_policy {
46 AdmissionFitPolicy::ImmediateOnly => self.work_shape.immediate_shape(),
47 AdmissionFitPolicy::FullInputMustFit => self.work_shape.fit_shape(),
48 }
49 }
50
51 pub const fn fit_policy(&self) -> AdmissionFitPolicy {
52 self.fit_policy
53 }
54
55 pub const fn pressure_action(&self) -> AdmissionPressureAction {
56 self.pressure_action
57 }
58
59 pub fn reusable_execution_bucket_id(&self) -> Option<&ReusableExecutionBucketId> {
60 self.reusable_execution_bucket_id.as_ref()
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
65pub struct BatchParticipantAuthority {
66 sequence_authority: SequenceAuthorityId,
67 request_authority: RequestAuthorityId,
68}
69
70impl BatchParticipantAuthority {
71 pub const fn new(
72 sequence_authority: SequenceAuthorityId,
73 request_authority: RequestAuthorityId,
74 ) -> Self {
75 Self {
76 sequence_authority,
77 request_authority,
78 }
79 }
80
81 pub const fn sequence_authority(self) -> SequenceAuthorityId {
82 self.sequence_authority
83 }
84
85 pub const fn request_authority(self) -> RequestAuthorityId {
86 self.request_authority
87 }
88
89 pub(super) const fn canonical_key(self) -> (u32, u64, u32, u64) {
90 (
91 self.sequence_authority.sparse_id(),
92 self.sequence_authority.generation(),
93 self.request_authority.sparse_id(),
94 self.request_authority.generation(),
95 )
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
103pub struct ParticipantNodeKey {
104 sequence_authority: SequenceAuthorityId,
105 request_authority: RequestAuthorityId,
106 frame_id: ExecutionFrameId,
107 node_id: NodeId,
108}
109
110impl ParticipantNodeKey {
111 pub(in crate::vnext) fn new(
112 participant: BatchParticipantAuthority,
113 frame_id: ExecutionFrameId,
114 node_id: NodeId,
115 ) -> Self {
116 Self {
117 sequence_authority: participant.sequence_authority(),
118 request_authority: participant.request_authority(),
119 frame_id,
120 node_id,
121 }
122 }
123
124 pub const fn sequence_authority(&self) -> SequenceAuthorityId {
125 self.sequence_authority
126 }
127
128 pub const fn request_authority(&self) -> RequestAuthorityId {
129 self.request_authority
130 }
131
132 pub const fn frame_id(&self) -> ExecutionFrameId {
133 self.frame_id
134 }
135
136 pub fn node_id(&self) -> &NodeId {
137 &self.node_id
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
144pub struct BatchParticipantTokenSpan {
145 participant: BatchParticipantAuthority,
146 token_span: TokenSpanWork,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
153pub struct BatchParticipantTokenRange {
154 participant: BatchParticipantAuthority,
155 immediate_start_token: u64,
156 immediate_end_token: u64,
157 source_start_token: u64,
158 source_end_token: u64,
159 full_input_tokens: u64,
160}
161
162impl BatchParticipantTokenRange {
163 fn new(
164 participant: BatchParticipantAuthority,
165 immediate_start_token: u64,
166 immediate_end_token: u64,
167 source_start_token: u64,
168 source_end_token: u64,
169 full_input_tokens: u64,
170 ) -> Result<Self, VNextError> {
171 if immediate_start_token >= immediate_end_token
172 || source_start_token >= source_end_token
173 || source_end_token > full_input_tokens
174 || immediate_end_token - immediate_start_token != source_end_token - source_start_token
175 {
176 return Err(invalid_resource(
177 "batch participant packed-token range is empty or exceeds its full input",
178 ));
179 }
180 Ok(Self {
181 participant,
182 immediate_start_token,
183 immediate_end_token,
184 source_start_token,
185 source_end_token,
186 full_input_tokens,
187 })
188 }
189
190 pub const fn participant(&self) -> BatchParticipantAuthority {
191 self.participant
192 }
193
194 pub fn immediate_token_range(&self) -> Range<u64> {
195 self.immediate_start_token..self.immediate_end_token
196 }
197
198 pub const fn immediate_tokens(&self) -> u64 {
199 self.immediate_end_token - self.immediate_start_token
200 }
201
202 pub fn source_token_range(&self) -> Range<u64> {
203 self.source_start_token..self.source_end_token
204 }
205
206 pub const fn full_input_tokens(&self) -> u64 {
207 self.full_input_tokens
208 }
209}
210
211impl BatchParticipantTokenSpan {
212 pub(super) fn new(participant: BatchParticipantAuthority, token_span: TokenSpanWork) -> Self {
213 Self {
214 participant,
215 token_span,
216 }
217 }
218
219 pub const fn participant(&self) -> BatchParticipantAuthority {
220 self.participant
221 }
222
223 pub fn token_span(&self) -> &TokenSpanWork {
224 &self.token_span
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
232pub struct BatchWorkShape {
233 participants: Vec<BatchParticipantAuthority>,
234 participant_work: Vec<BatchParticipantTokenSpan>,
235 participant_token_ranges: Vec<BatchParticipantTokenRange>,
236 resource_work: ResourceWorkShape,
237 fingerprint: String,
238}
239
240impl BatchWorkShape {
241 pub(super) fn new(
242 participant_work: Vec<BatchParticipantTokenSpan>,
243 ) -> Result<Self, VNextError> {
244 if participant_work.is_empty()
245 || participant_work.windows(2).any(|pair| {
246 pair[0].participant().canonical_key() >= pair[1].participant().canonical_key()
247 })
248 {
249 return Err(invalid_resource(
250 "batch work shape requires canonical non-empty unique participant work",
251 ));
252 }
253 let participants = participant_work
254 .iter()
255 .map(BatchParticipantTokenSpan::participant)
256 .collect::<Vec<_>>();
257 let mut next_token = 0_u64;
258 let participant_token_ranges = participant_work
259 .iter()
260 .map(|work| {
261 let start = next_token;
262 next_token = next_token
263 .checked_add(work.token_span().immediate_tokens())
264 .ok_or_else(|| invalid_resource("packed batch token range overflows u64"))?;
265 BatchParticipantTokenRange::new(
266 work.participant(),
267 start,
268 next_token,
269 work.token_span().immediate_token_range().start,
270 work.token_span().immediate_token_range().end,
271 work.token_span().full_input_tokens(),
272 )
273 })
274 .collect::<Result<Vec<_>, VNextError>>()?;
275 let resource_work = ResourceWorkShape::from_token_spans(
276 participant_work
277 .iter()
278 .map(|work| work.token_span().clone())
279 .collect(),
280 )?;
281 if resource_work.immediate_sequences()
282 != u32::try_from(participants.len())
283 .map_err(|_| invalid_resource("batch work participant count exceeds u32"))?
284 || next_token != resource_work.immediate_tokens()
285 {
286 return Err(invalid_resource(
287 "batch work shape aggregate differs from participant evidence",
288 ));
289 }
290 #[derive(Serialize)]
291 struct FingerprintInput<'a> {
292 domain: &'static str,
293 participant_work: &'a [BatchParticipantTokenSpan],
294 participant_token_ranges: &'a [BatchParticipantTokenRange],
295 resource_work_fingerprint: &'a str,
296 }
297 let input = FingerprintInput {
298 domain: "ferrum.runtime-vnext.batch-work-shape.v3",
299 participant_work: &participant_work,
300 participant_token_ranges: &participant_token_ranges,
301 resource_work_fingerprint: resource_work.fingerprint(),
302 };
303 let bytes = serde_json::to_vec(&input).map_err(|error| {
304 invalid_resource(format!("batch work shape encode failed: {error}"))
305 })?;
306 Ok(Self {
307 participants,
308 participant_work,
309 participant_token_ranges,
310 resource_work,
311 fingerprint: format!("{:x}", Sha256::digest(bytes)),
312 })
313 }
314
315 #[cfg(test)]
316 pub(crate) fn test_only(token_spans: Vec<TokenSpanWork>) -> Result<Self, VNextError> {
317 let participant_work = token_spans
318 .into_iter()
319 .enumerate()
320 .map(|(index, token_span)| {
321 let sparse_id =
322 u32::try_from(index + 1).expect("bounded test participant index fits u32");
323 BatchParticipantTokenSpan::new(
324 BatchParticipantAuthority::new(
325 SequenceAuthorityId::test_only(sparse_id, 1),
326 RequestAuthorityId::test_only(sparse_id, 1),
327 ),
328 token_span,
329 )
330 })
331 .collect();
332 Self::new(participant_work)
333 }
334
335 pub fn participants(&self) -> &[BatchParticipantAuthority] {
336 &self.participants
337 }
338
339 pub fn participant_work(&self) -> &[BatchParticipantTokenSpan] {
340 &self.participant_work
341 }
342
343 pub fn participant_token_ranges(&self) -> &[BatchParticipantTokenRange] {
344 &self.participant_token_ranges
345 }
346
347 pub fn resource_work(&self) -> &ResourceWorkShape {
348 &self.resource_work
349 }
350
351 pub const fn immediate_sequences(&self) -> u32 {
352 self.resource_work.immediate_sequences()
353 }
354
355 pub const fn immediate_tokens(&self) -> u64 {
356 self.resource_work.immediate_tokens()
357 }
358
359 pub const fn immediate_pages(&self) -> u64 {
360 self.resource_work.immediate_pages()
361 }
362
363 pub const fn fit_sequences(&self) -> u32 {
364 self.resource_work.fit_sequences()
365 }
366
367 pub const fn fit_tokens(&self) -> u64 {
368 self.resource_work.fit_tokens()
369 }
370
371 pub const fn fit_pages(&self) -> u64 {
372 self.resource_work.fit_pages()
373 }
374
375 pub fn fingerprint(&self) -> &str {
376 &self.fingerprint
377 }
378
379 pub fn logical_work_fingerprint(&self) -> &str {
383 self.resource_work.fingerprint()
384 }
385
386 pub(crate) const fn immediate_shape(&self) -> DynamicResourceShape {
387 self.resource_work.immediate_shape()
388 }
389
390 pub(crate) const fn fit_shape(&self) -> DynamicResourceShape {
391 self.resource_work.fit_shape()
392 }
393}
394
395#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
396pub struct StepParticipantFrameAssignment {
397 participant: BatchParticipantAuthority,
398 frame_id: ExecutionFrameId,
399}
400
401impl StepParticipantFrameAssignment {
402 pub(super) const fn new(
403 sequence_authority: SequenceAuthorityId,
404 request_authority: RequestAuthorityId,
405 frame_id: ExecutionFrameId,
406 ) -> Self {
407 Self {
408 participant: BatchParticipantAuthority::new(sequence_authority, request_authority),
409 frame_id,
410 }
411 }
412
413 pub const fn participant(self) -> BatchParticipantAuthority {
414 self.participant
415 }
416
417 pub const fn sequence_authority(self) -> SequenceAuthorityId {
418 self.participant.sequence_authority()
419 }
420
421 pub const fn request_authority(self) -> RequestAuthorityId {
422 self.participant.request_authority()
423 }
424
425 pub const fn frame_id(self) -> ExecutionFrameId {
426 self.frame_id
427 }
428
429 const fn canonical_key(self) -> (u32, u64, u32, u64) {
430 self.participant.canonical_key()
431 }
432}
433
434#[derive(Debug, Clone, PartialEq, Eq)]
435pub struct InvocationResourceAdmissionRequest {
436 pub(super) node_id: NodeId,
437 pub(super) work_shape: Arc<BatchWorkShape>,
438 pub(super) fit_policy: AdmissionFitPolicy,
439 pub(super) pressure_action: AdmissionPressureAction,
440}
441
442impl InvocationResourceAdmissionRequest {
443 pub fn new(
444 node_id: NodeId,
445 work_shape: impl Into<Arc<BatchWorkShape>>,
446 fit_policy: AdmissionFitPolicy,
447 pressure_action: AdmissionPressureAction,
448 ) -> Result<Self, VNextError> {
449 Ok(Self {
450 node_id,
451 work_shape: work_shape.into(),
452 fit_policy,
453 pressure_action,
454 })
455 }
456
457 pub fn for_all_step_participants(
458 node_id: NodeId,
459 work_shape: impl Into<Arc<BatchWorkShape>>,
460 fit_policy: AdmissionFitPolicy,
461 pressure_action: AdmissionPressureAction,
462 ) -> Result<Self, VNextError> {
463 Self::new(node_id, work_shape, fit_policy, pressure_action)
464 }
465
466 pub fn node_id(&self) -> &NodeId {
467 &self.node_id
468 }
469
470 pub fn work_shape(&self) -> &BatchWorkShape {
471 self.work_shape.as_ref()
472 }
473
474 pub(crate) fn immediate_shape(&self) -> DynamicResourceShape {
475 self.work_shape.immediate_shape()
476 }
477
478 pub(crate) fn fit_shape(&self) -> DynamicResourceShape {
479 match self.fit_policy {
480 AdmissionFitPolicy::ImmediateOnly => self.work_shape.immediate_shape(),
481 AdmissionFitPolicy::FullInputMustFit => self.work_shape.fit_shape(),
482 }
483 }
484
485 pub const fn fit_policy(&self) -> AdmissionFitPolicy {
486 self.fit_policy
487 }
488
489 pub const fn pressure_action(&self) -> AdmissionPressureAction {
490 self.pressure_action
491 }
492}