1mod mock_helpers;
27#[cfg(test)]
28mod tests;
29
30use crate::{configuration, on_demand, ParaId};
31
32use alloc::{
33 collections::{BTreeMap, VecDeque},
34 vec::Vec,
35};
36use frame_support::{defensive, pallet_prelude::*};
37use frame_system::pallet_prelude::*;
38use polkadot_primitives::CoreIndex;
39use scale_info::TypeInfo;
40use sp_runtime::{
41 codec::{Decode, Encode},
42 traits::Saturating,
43 Debug,
44};
45
46pub use pallet_broker::CoreAssignment;
47
48pub use super::Config;
49
50#[derive(
52 Debug,
53 Clone,
54 Copy,
55 PartialEq,
56 Eq,
57 PartialOrd,
58 Ord,
59 Encode,
60 Decode,
61 DecodeWithMemTracking,
62 TypeInfo,
63)]
64pub struct PartsOf57600(u16);
65
66impl PartsOf57600 {
67 pub const ZERO: Self = Self(0);
68 pub const FULL: Self = Self(57600);
69
70 pub fn new_saturating(v: u16) -> Self {
71 Self::ZERO.saturating_add(Self(v))
72 }
73
74 #[cfg(test)]
76 pub fn value(&self) -> u16 {
77 self.0
78 }
79
80 pub fn is_full(&self) -> bool {
81 *self == Self::FULL
82 }
83
84 pub fn saturating_add(self, rhs: Self) -> Self {
85 let inner = self.0.saturating_add(rhs.0);
86 if inner > 57600 {
87 Self(57600)
88 } else {
89 Self(inner)
90 }
91 }
92
93 pub fn saturating_sub(self, rhs: Self) -> Self {
94 Self(self.0.saturating_sub(rhs.0))
95 }
96
97 pub fn checked_add(self, rhs: Self) -> Option<Self> {
98 let inner = self.0.saturating_add(rhs.0);
99 if inner > 57600 {
100 None
101 } else {
102 Some(Self(inner))
103 }
104 }
105}
106
107#[derive(Encode, Decode, TypeInfo)]
111#[cfg_attr(test, derive(PartialEq, Debug))]
112pub struct Schedule<N> {
113 assignments: Vec<(CoreAssignment, PartsOf57600)>,
115 end_hint: Option<N>,
121
122 next_schedule: Option<N>,
126}
127
128impl<N> Schedule<N> {
129 #[cfg(test)]
131 pub fn new(
132 assignments: Vec<(CoreAssignment, PartsOf57600)>,
133 end_hint: Option<N>,
134 next_schedule: Option<N>,
135 ) -> Self {
136 Self { assignments, end_hint, next_schedule }
137 }
138
139 pub fn assignments(&self) -> &[(CoreAssignment, PartsOf57600)] {
141 &self.assignments
142 }
143
144 #[cfg(test)]
146 pub fn end_hint(&self) -> Option<N>
147 where
148 N: Copy,
149 {
150 self.end_hint
151 }
152
153 pub(super) fn next_schedule(&self) -> Option<N>
155 where
156 N: Copy,
157 {
158 self.next_schedule
159 }
160}
161
162#[derive(Encode, Decode, TypeInfo, Default)]
167#[cfg_attr(test, derive(PartialEq, Debug, Clone))]
168pub struct CoreDescriptor<N> {
169 queue: Option<QueueDescriptor<N>>,
171 current_work: Option<WorkState<N>>,
173}
174
175impl<N: PartialOrd> CoreDescriptor<N> {
176 #[cfg(test)]
178 pub(super) fn new(
179 queue: Option<QueueDescriptor<N>>,
180 current_work: Option<WorkState<N>>,
181 ) -> Self {
182 Self { queue, current_work }
183 }
184
185 fn has_assignments_until(&self, until: N) -> bool {
189 self.current_work.is_some() || self.queue.as_ref().map_or(false, |q| q.first < until)
190 }
191
192 pub fn queue(&self) -> Option<&QueueDescriptor<N>> {
194 self.queue.as_ref()
195 }
196
197 pub fn current_work(&self) -> Option<&WorkState<N>> {
199 self.current_work.as_ref()
200 }
201}
202
203#[derive(Encode, Decode, TypeInfo, Copy, Clone)]
208#[cfg_attr(test, derive(PartialEq, Debug))]
209pub struct QueueDescriptor<N> {
210 pub first: N,
212 pub last: N,
214}
215
216#[derive(Encode, Decode, TypeInfo)]
217#[cfg_attr(test, derive(PartialEq, Debug, Clone))]
218pub struct WorkState<N> {
219 pub assignments: Vec<(CoreAssignment, AssignmentState)>,
224 pub end_hint: Option<N>,
230 pub pos: u16,
235 pub step: PartsOf57600,
239}
240
241#[derive(Encode, Decode, TypeInfo)]
242#[cfg_attr(test, derive(PartialEq, Debug, Clone, Copy))]
243pub struct AssignmentState {
244 pub ratio: PartsOf57600,
248 pub remaining: PartsOf57600,
257}
258
259enum AccessMode<'a, T: Config> {
261 Peek { on_demand_orders: &'a mut on_demand::OrderQueue<BlockNumberFor<T>> },
263 Pop,
265}
266
267impl<'a, T: Config> AccessMode<'a, T> {
268 fn peek(on_demand_orders: &'a mut on_demand::OrderQueue<BlockNumberFor<T>>) -> Self {
270 Self::Peek { on_demand_orders }
271 }
272
273 fn pop() -> Self {
275 Self::Pop
276 }
277
278 fn pop_assignment_for_ondemand_cores(
280 &mut self,
281 now: BlockNumberFor<T>,
282 num_cores: u32,
283 ) -> impl Iterator<Item = ParaId> {
284 match self {
285 Self::Peek { on_demand_orders } => on_demand_orders
286 .pop_assignment_for_cores::<T>(now, num_cores)
287 .collect::<Vec<_>>(),
288 Self::Pop => {
289 on_demand::Pallet::<T>::pop_assignment_for_cores(now, num_cores).collect::<Vec<_>>()
290 },
291 }
292 .into_iter()
293 }
294
295 fn get_core_schedule(
297 &self,
298 next_scheduled: BlockNumberFor<T>,
299 core_idx: CoreIndex,
300 ) -> Option<Schedule<BlockNumberFor<T>>> {
301 match self {
302 Self::Peek { .. } => super::CoreSchedules::<T>::get((next_scheduled, core_idx)),
303 Self::Pop => super::CoreSchedules::<T>::take((next_scheduled, core_idx)),
304 }
305 }
306}
307
308struct AdvancedAssignments {
310 bulk_assignments: Vec<(CoreIndex, ParaId)>,
311 pool_assignments: Vec<(CoreIndex, ParaId)>,
312}
313
314impl AdvancedAssignments {
315 fn into_iter(self) -> impl Iterator<Item = (CoreIndex, ParaId)> {
316 let Self { bulk_assignments, pool_assignments } = self;
317 bulk_assignments.into_iter().chain(pool_assignments.into_iter())
318 }
319}
320
321impl<N> From<Schedule<N>> for WorkState<N> {
322 fn from(schedule: Schedule<N>) -> Self {
323 let Schedule { assignments, end_hint, next_schedule: _ } = schedule;
324 let step =
325 if let Some(min_step_assignment) = assignments.iter().min_by(|a, b| a.1.cmp(&b.1)) {
326 min_step_assignment.1
327 } else {
328 log::debug!("assignments of a `Schedule` should never be empty.");
330 PartsOf57600(1)
331 };
332 let assignments = assignments
333 .into_iter()
334 .map(|(a, ratio)| (a, AssignmentState { ratio, remaining: ratio }))
335 .collect();
336
337 Self { assignments, end_hint, pos: 0, step }
338 }
339}
340
341#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
342pub enum Error {
343 AssignmentsEmpty,
344 DisallowedInsert,
347}
348
349pub(super) fn peek_next_block<T: super::Config>(
367 num_entries: u32,
368) -> BTreeMap<CoreIndex, VecDeque<ParaId>> {
369 let now = frame_system::Pallet::<T>::block_number().saturating_plus_one();
370 peek_impl::<T>(now, num_entries)
371}
372
373pub(super) fn advance_assignments<T: Config, F: Fn(CoreIndex) -> bool>(
385 is_blocked: F,
386) -> BTreeMap<CoreIndex, ParaId> {
387 let now = frame_system::Pallet::<T>::block_number();
388
389 let assignments = super::CoreDescriptors::<T>::mutate(|core_states| {
390 advance_assignments_single_impl::<T>(now, core_states, AccessMode::<T>::pop())
391 });
392
393 for blocked in assignments.pool_assignments.iter().filter_map(|(core_idx, para_id)| {
395 if is_blocked(*core_idx) {
396 Some(*para_id)
397 } else {
398 None
399 }
400 }) {
401 on_demand::Pallet::<T>::push_back_order(blocked);
402 }
403
404 let mut assignments: BTreeMap<CoreIndex, ParaId> =
405 assignments.into_iter().filter(|(core_idx, _)| !is_blocked(*core_idx)).collect();
406
407 let next = now.saturating_plus_one();
410 let mut core_states = super::CoreDescriptors::<T>::get();
411 let mut on_demand_orders = on_demand::Pallet::<T>::peek_order_queue();
412 let next_assignments = advance_assignments_single_impl(
413 next,
414 &mut core_states,
415 AccessMode::<T>::peek(&mut on_demand_orders),
416 )
417 .into_iter();
418
419 for (core_idx, next_assignment) in
420 next_assignments.filter(|(core_idx, _)| !is_blocked(*core_idx))
421 {
422 assignments.entry(core_idx).or_insert_with(|| next_assignment);
423 }
424 assignments
425}
426
427pub(super) fn assign_core<T: Config>(
450 core_idx: CoreIndex,
451 mut begin: BlockNumberFor<T>,
452 mut assignments: Vec<(CoreAssignment, PartsOf57600)>,
453 end_hint: Option<BlockNumberFor<T>>,
454) -> Result<(), Error> {
455 ensure!(!assignments.is_empty(), Error::AssignmentsEmpty);
457
458 super::CoreDescriptors::<T>::mutate(|core_descriptors| {
459 let core_descriptor = core_descriptors.entry(core_idx).or_default();
460
461 let config = configuration::ActiveConfig::<T>::get();
462 let now = frame_system::Pallet::<T>::block_number().saturating_plus_one();
464 let lookahead = config.scheduler_params.lookahead.into();
465 let claim_queue_end = now.saturating_add(lookahead);
466 let assignments_exist = core_descriptor.has_assignments_until(claim_queue_end);
467 if assignments_exist && begin < claim_queue_end {
470 log::debug!(
471 target: "runtime::parachains::assigner-coretime",
472 "Claim queue needs to be stable, schedule change within claim queue length is not supported. Adjusting begin from {:?} to {:?}",
473 begin,
474 claim_queue_end
475 );
476 begin = claim_queue_end;
477 }
478
479 let new_queue = match core_descriptor.queue {
480 Some(queue) => {
481 ensure!(begin >= queue.last, Error::DisallowedInsert);
482
483 if begin > queue.last {
485 super::CoreSchedules::<T>::mutate((queue.last, core_idx), |schedule| {
486 if let Some(schedule) = schedule.as_mut() {
487 debug_assert!(schedule.next_schedule.is_none(), "queue.end was supposed to be the end, so the next item must be `None`!");
488 schedule.next_schedule = Some(begin);
489 } else {
490 defensive!("Queue end entry does not exist?");
491 }
492 });
493 }
494
495 super::CoreSchedules::<T>::mutate((begin, core_idx), |schedule| {
496 let assignments = if let Some(mut old_schedule) = schedule.take() {
497 old_schedule.assignments.append(&mut assignments);
498 old_schedule.assignments
499 } else {
500 assignments
501 };
502 *schedule = Some(Schedule { assignments, end_hint, next_schedule: None });
503 });
504
505 QueueDescriptor { first: queue.first, last: begin }
506 },
507 None => {
508 super::CoreSchedules::<T>::insert(
510 (begin, core_idx),
511 Schedule { assignments, end_hint, next_schedule: None },
512 );
513 QueueDescriptor { first: begin, last: begin }
514 },
515 };
516 core_descriptor.queue = Some(new_queue);
517 Ok(())
518 })
519}
520
521fn num_coretime_cores<T: Config>() -> u32 {
522 configuration::ActiveConfig::<T>::get().scheduler_params.num_cores
523}
524
525fn peek_impl<T: Config>(
526 mut now: BlockNumberFor<T>,
527 num_entries: u32,
528) -> BTreeMap<CoreIndex, VecDeque<ParaId>> {
529 let mut core_states = super::CoreDescriptors::<T>::get();
530 let mut result = BTreeMap::new();
531 let mut on_demand_orders = on_demand::Pallet::<T>::peek_order_queue();
532 for i in 0..num_entries {
533 let assignments = advance_assignments_single_impl(
534 now,
535 &mut core_states,
536 AccessMode::<T>::peek(&mut on_demand_orders),
537 )
538 .into_iter();
539 for (core_idx, para_id) in assignments {
540 let claim_queue: &mut VecDeque<ParaId> = result.entry(core_idx).or_default();
541 if claim_queue.len() == i as usize {
543 claim_queue.push_back(para_id)
544 } else if claim_queue.len() == 0 && i == 1 {
545 claim_queue.push_back(para_id);
550 claim_queue.push_back(para_id);
552 }
553 }
554 now.saturating_inc();
555 }
556 result
557}
558
559fn advance_assignments_single_impl<T: Config>(
561 now: BlockNumberFor<T>,
562 core_states: &mut BTreeMap<CoreIndex, CoreDescriptor<BlockNumberFor<T>>>,
563 mut mode: AccessMode<T>,
564) -> AdvancedAssignments {
565 let mut bulk_assignments = Vec::with_capacity(num_coretime_cores::<T>() as _);
566 let mut pool_cores = Vec::with_capacity(num_coretime_cores::<T>() as _);
567 for (core_idx, core_state) in core_states.iter_mut() {
568 ensure_workload::<T>(now, *core_idx, core_state, &mode);
569
570 let Some(work_state) = core_state.current_work.as_mut() else { continue };
571
572 work_state.pos = work_state.pos % work_state.assignments.len() as u16;
574 let (a_type, a_state) = &mut work_state
575 .assignments
576 .get_mut(work_state.pos as usize)
577 .expect("We limited pos to the size of the vec one line above. qed");
578
579 a_state.remaining = a_state.remaining.saturating_sub(work_state.step);
581 if a_state.remaining < work_state.step {
582 work_state.pos += 1;
585 a_state.remaining = a_state.remaining.saturating_add(a_state.ratio);
587 }
588 match *a_type {
589 CoreAssignment::Pool => pool_cores.push(*core_idx),
590 CoreAssignment::Task(para_id) => bulk_assignments.push((*core_idx, para_id.into())),
591 CoreAssignment::Idle => {},
592 }
593 }
594
595 let pool_assignments = mode.pop_assignment_for_ondemand_cores(now, pool_cores.len() as _);
596 let pool_assignments = pool_cores.into_iter().zip(pool_assignments).collect();
597
598 AdvancedAssignments { bulk_assignments, pool_assignments }
599}
600
601fn ensure_workload<T: Config>(
603 now: BlockNumberFor<T>,
604 core_idx: CoreIndex,
605 descriptor: &mut CoreDescriptor<BlockNumberFor<T>>,
606 mode: &AccessMode<T>,
607) {
608 if descriptor
610 .current_work
611 .as_ref()
612 .and_then(|w| w.end_hint)
613 .map_or(false, |e| e <= now)
614 {
615 descriptor.current_work = None;
616 }
617
618 let Some(queue) = descriptor.queue else {
619 return;
621 };
622
623 let mut next_scheduled = queue.first;
624
625 if next_scheduled > now {
626 return;
628 }
629
630 let update = loop {
632 let Some(update) = mode.get_core_schedule(next_scheduled, core_idx) else { break None };
633
634 if update.end_hint.map_or(true, |e| e > now) {
636 break Some(update);
637 }
638 if let Some(n) = update.next_schedule {
640 next_scheduled = n;
641 } else {
642 break None;
643 }
644 };
645
646 let new_first = update.as_ref().and_then(|u| u.next_schedule);
647 descriptor.current_work = update.map(Into::into);
648
649 descriptor.queue = new_first.map(|new_first| {
650 QueueDescriptor {
651 first: new_first,
652 last: queue.last,
654 }
655 });
656}