Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions prdoc/pr_7790.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
title: 'pallet-scheduler: Put back postponed tasks into the agenda'
doc:
- audience: Runtime Dev
description: "Right now `pallet-scheduler` is not putting back postponed tasks into\
\ the agenda when the early weight check is failing. This pull request ensures\
\ that these tasks are put back into the agenda and are not just \"lost\".\r\n"
crates:
- name: pallet-scheduler
bump: patch
10 changes: 6 additions & 4 deletions substrate/frame/scheduler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ pub mod pallet {
type BlockNumberProvider: BlockNumberProvider;
}

/// Block number at which the agenda began incomplete execution.
#[pallet::storage]
pub type IncompleteSince<T: Config> = StorageValue<_, BlockNumberFor<T>>;

Expand Down Expand Up @@ -386,6 +387,8 @@ pub mod pallet {
RetryFailed { task: TaskAddress<BlockNumberFor<T>>, id: Option<TaskName> },
/// The given task can never be executed since it is overweight.
PermanentlyOverweight { task: TaskAddress<BlockNumberFor<T>>, id: Option<TaskName> },
/// Agenda is incomplete from `when`.
AgendaIncomplete { when: BlockNumberFor<T> },
}

#[pallet::error]
Expand Down Expand Up @@ -1202,6 +1205,7 @@ impl<T: Config> Pallet<T> {
}
incomplete_since = incomplete_since.min(when);
if incomplete_since <= now {
Self::deposit_event(Event::AgendaIncomplete { when: incomplete_since });
IncompleteSince::<T>::put(incomplete_since);
}
}
Expand Down Expand Up @@ -1235,17 +1239,15 @@ impl<T: Config> Pallet<T> {
let mut dropped = 0;

for (agenda_index, _) in ordered.into_iter().take(max as usize) {
let task = match agenda[agenda_index as usize].take() {
None => continue,
Some(t) => t,
};
let Some(task) = agenda[agenda_index as usize].take() else { continue };
let base_weight = T::WeightInfo::service_task(
task.call.lookup_len().map(|x| x as usize),
task.maybe_id.is_some(),
task.maybe_periodic.is_some(),
);
if !weight.can_consume(base_weight) {
postponed += 1;
agenda[agenda_index as usize] = Some(task);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so this is what was missing from before: the task would have been taken above, and not put back in the agenda.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

break
}
let result = Self::service_task(weight, now, when, agenda_index, *executed == 0, task);
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/scheduler/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl WeightInfo for TestWeightInfo {
}
}
parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
pub storage MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
}

Expand Down
57 changes: 47 additions & 10 deletions substrate/frame/scheduler/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1264,8 +1264,8 @@ fn cancel_named_periodic_scheduling_works() {

#[test]
fn scheduler_respects_weight_limits() {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
new_test_ext().execute_with(|| {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 * 2 });
assert_ok!(Scheduler::do_schedule(
DispatchTime::At(4),
Expand All @@ -1292,8 +1292,8 @@ fn scheduler_respects_weight_limits() {

#[test]
fn retry_respects_weight_limits() {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
new_test_ext().execute_with(|| {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
// schedule 42
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 * 2 });
assert_ok!(Scheduler::do_schedule(
Expand Down Expand Up @@ -1344,8 +1344,8 @@ fn retry_respects_weight_limits() {

#[test]
fn try_schedule_retry_respects_weight_limits() {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
new_test_ext().execute_with(|| {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
let service_agendas_weight = <Test as Config>::WeightInfo::service_agendas_base();
let service_agenda_weight = <Test as Config>::WeightInfo::service_agenda_base(
<Test as Config>::MaxScheduledPerBlock::get(),
Expand Down Expand Up @@ -1404,8 +1404,8 @@ fn try_schedule_retry_respects_weight_limits() {
/// Permanently overweight calls are not deleted but also not executed.
#[test]
fn scheduler_does_not_delete_permanently_overweight_call() {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
new_test_ext().execute_with(|| {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight });
assert_ok!(Scheduler::do_schedule(
DispatchTime::At(4),
Expand All @@ -1430,10 +1430,10 @@ fn scheduler_does_not_delete_permanently_overweight_call() {

#[test]
fn scheduler_handles_periodic_failure() {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
let max_per_block = <Test as Config>::MaxScheduledPerBlock::get();

new_test_ext().execute_with(|| {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
let max_per_block = <Test as Config>::MaxScheduledPerBlock::get();

let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: (max_weight / 3) * 2 });
let bound = Preimage::bound(call).unwrap();

Expand Down Expand Up @@ -1472,9 +1472,9 @@ fn scheduler_handles_periodic_failure() {

#[test]
fn scheduler_handles_periodic_unavailable_preimage() {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();

new_test_ext().execute_with(|| {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();

let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: (max_weight / 3) * 2 });
let hash = <Test as frame_system::Config>::Hashing::hash_of(&call);
let len = call.using_encoded(|x| x.len()) as u32;
Expand Down Expand Up @@ -1518,8 +1518,8 @@ fn scheduler_handles_periodic_unavailable_preimage() {

#[test]
fn scheduler_respects_priority_ordering() {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
new_test_ext().execute_with(|| {
let max_weight: Weight = <Test as Config>::MaximumWeight::get();
let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 });
assert_ok!(Scheduler::do_schedule(
DispatchTime::At(4),
Expand Down Expand Up @@ -3039,3 +3039,40 @@ fn unavailable_call_is_detected() {
assert!(!Preimage::is_requested(&hash));
});
}

#[test]
fn postponed_task_is_still_available() {
new_test_ext().execute_with(|| {
let service_agendas_weight = <Test as Config>::WeightInfo::service_agendas_base();
let service_agenda_weight = <Test as Config>::WeightInfo::service_agenda_base(
<Test as Config>::MaxScheduledPerBlock::get(),
);

assert_ok!(Scheduler::schedule(
RuntimeOrigin::root(),
4,
None,
128,
Box::new(RuntimeCall::from(frame_system::Call::remark {
remark: vec![0u8; 3 * 1024 * 1024],
}))
));
System::run_to_block::<AllPalletsWithSystem>(3);
// Scheduled calls are in the agenda.
assert_eq!(Agenda::<Test>::get(4).len(), 1);

let old_weight = MaximumSchedulerWeight::get();
MaximumSchedulerWeight::set(&service_agenda_weight.saturating_add(service_agendas_weight));

System::run_to_block::<AllPalletsWithSystem>(4);

// The task should still be there.
assert_eq!(Agenda::<Test>::get(4).iter().filter(|a| a.is_some()).count(), 1);
System::assert_last_event(crate::Event::AgendaIncomplete { when: 4 }.into());

// Now it should get executed
MaximumSchedulerWeight::set(&old_weight);
System::run_to_block::<AllPalletsWithSystem>(5);
assert!(Agenda::<Test>::get(4).is_empty());
});
}
Loading