Skip to content

Commit a4b29e1

Browse files
rockbmbseadanda
andauthored
Minor pallet-scheduler documentation/unit test additions (#10511)
# Description This adds some comments to scheduler pallet calls, a couple of checks to `Lookup<T>` in some unit tests, and a test that verifies rescheduling of a task to a full block. ## Integration N/A ## Review Notes N/A --------- Co-authored-by: Dónal Murray <[email protected]>
1 parent ea81e0b commit a4b29e1

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed

prdoc/pr_10511.prdoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
title: Minor pallet-scheduler documentation/unit test additions
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
This PR changes the Rust documentation of some extrinsic calls in the scheduler pallet.
6+
7+
It also adds some checks to the `Lookup<T>` storage item in a unit test to named task cancellation via
8+
the anonymous cancellation call, and a unit test for the rescheduling of a failed named/anonymous task
9+
to a full agenda.
10+
crates:
11+
- name: pallet-scheduler
12+
bump: patch

substrate/frame/scheduler/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,10 @@ pub mod pallet {
484484
Ok(())
485485
}
486486

487-
/// Cancel an anonymously scheduled task.
487+
/// Cancel a scheduled task (named or anonymous), by providing the block it is scheduled for
488+
/// execution in, as well as the index of the task in that block's agenda.
489+
///
490+
/// In the case of a named task, it will remove it from the lookup table as well.
488491
#[pallet::call_index(1)]
489492
#[pallet::weight(<T as Config>::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))]
490493
pub fn cancel(origin: OriginFor<T>, when: BlockNumberFor<T>, index: u32) -> DispatchResult {
@@ -586,6 +589,8 @@ pub mod pallet {
586589
/// clones of the original task. Their retry configuration will be derived from the
587590
/// original task's configuration, but will have a lower value for `remaining` than the
588591
/// original `total_retries`.
592+
///
593+
/// This call **cannot** be used to set a retry configuration for a named task.
589594
#[pallet::call_index(6)]
590595
#[pallet::weight(<T as Config>::WeightInfo::set_retry())]
591596
pub fn set_retry(
@@ -623,6 +628,8 @@ pub mod pallet {
623628
/// clones of the original task. Their retry configuration will be derived from the
624629
/// original task's configuration, but will have a lower value for `remaining` than the
625630
/// original `total_retries`.
631+
///
632+
/// This is the only way to set a retry configuration for a named task.
626633
#[pallet::call_index(7)]
627634
#[pallet::weight(<T as Config>::WeightInfo::set_retry_named())]
628635
pub fn set_retry_named(
@@ -1021,11 +1028,16 @@ impl<T: Config> Pallet<T> {
10211028
fn cleanup_agenda(when: BlockNumberFor<T>) {
10221029
let mut agenda = Agenda::<T>::get(when);
10231030
match agenda.iter().rposition(|i| i.is_some()) {
1031+
// Note that `agenda.len() > i + 1` implies that the agenda ends on a sequence of at
1032+
// least one `None` item(s).
10241033
Some(i) if agenda.len() > i + 1 => {
10251034
agenda.truncate(i + 1);
10261035
Agenda::<T>::insert(when, agenda);
10271036
},
1037+
// This branch is taken if `agenda.len() <= i + 1 ==> agenda.len() == i + 1 <==>
1038+
// agenda.len() - 1 == i` i.e. the agenda's last item is `Some`.
10281039
Some(_) => {},
1040+
// All items in the agenda are `None`.
10291041
None => {
10301042
Agenda::<T>::remove(when);
10311043
},

substrate/frame/scheduler/src/tests.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,91 @@ fn try_schedule_retry_respects_weight_limits() {
14011401
});
14021402
}
14031403

1404+
fn schedule_retry_fails_when_retry_target_block_is_full(named: bool) {
1405+
let max: u32 = <Test as Config>::MaxScheduledPerBlock::get();
1406+
let retry_period = 3;
1407+
let task_name = [42u8; 32];
1408+
1409+
new_test_ext().execute_with(|| {
1410+
// Task will fail until block 100 (effectively always fails in this test).
1411+
Threshold::<Test>::put((100, 200));
1412+
1413+
// Fill block 7 (4 + retry_period) to capacity with dummy tasks.
1414+
let filler_call =
1415+
RuntimeCall::Logger(LoggerCall::log { i: 99, weight: Weight::from_parts(10, 0) });
1416+
let filler_bound = Preimage::bound(filler_call).unwrap();
1417+
for _ in 0..max {
1418+
assert_ok!(Scheduler::do_schedule(
1419+
DispatchTime::At(4 + retry_period),
1420+
None,
1421+
127,
1422+
root(),
1423+
filler_bound.clone(),
1424+
));
1425+
}
1426+
assert_eq!(Agenda::<Test>::get(4 + retry_period).len() as u32, max);
1427+
1428+
// Schedule a task at block 4 that will fail.
1429+
let failing_call =
1430+
RuntimeCall::Logger(LoggerCall::timed_log { i: 42, weight: Weight::from_parts(10, 0) });
1431+
if named {
1432+
assert_ok!(Scheduler::do_schedule_named(
1433+
task_name,
1434+
DispatchTime::At(4),
1435+
None,
1436+
127,
1437+
root(),
1438+
Preimage::bound(failing_call).unwrap(),
1439+
));
1440+
// Set retry config for named task.
1441+
assert_ok!(Scheduler::set_retry_named(root().into(), task_name, 10, retry_period));
1442+
} else {
1443+
assert_ok!(Scheduler::do_schedule(
1444+
DispatchTime::At(4),
1445+
None,
1446+
127,
1447+
root(),
1448+
Preimage::bound(failing_call).unwrap(),
1449+
));
1450+
// Set retry config for anonymous task.
1451+
assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, retry_period));
1452+
}
1453+
assert_eq!(Retries::<Test>::iter().count(), 1);
1454+
1455+
// Run to block 4: task fails and tries to schedule retry at block 7, but it's full.
1456+
System::run_to_block::<AllPalletsWithSystem>(4);
1457+
1458+
// The retry config should be removed since scheduling failed.
1459+
assert_eq!(Retries::<Test>::iter().count(), 0);
1460+
// Task at block 4 should be gone.
1461+
assert!(Agenda::<Test>::get(4).is_empty());
1462+
// Block 7 should still have exactly `max` tasks (the fillers, no retry added).
1463+
assert_eq!(Agenda::<Test>::get(4 + retry_period).len() as u32, max);
1464+
// Task 42 never executed.
1465+
assert!(!logger::log().iter().any(|(_, i)| *i == 42));
1466+
1467+
// Verify `RetryFailed` event was emitted.
1468+
// Note: `id` is always `None` because `as_retry()` clears the task name.
1469+
let events = frame_system::Pallet::<Test>::events();
1470+
let retry_failed_event: <Test as frame_system::Config>::RuntimeEvent =
1471+
Event::RetryFailed { task: (4, 0), id: None }.into();
1472+
assert!(
1473+
events.iter().any(|record| record.event == retry_failed_event),
1474+
"Expected RetryFailed event not found"
1475+
);
1476+
});
1477+
}
1478+
1479+
#[test]
1480+
fn schedule_retry_fails_when_retry_target_block_is_full_anon() {
1481+
schedule_retry_fails_when_retry_target_block_is_full(false);
1482+
}
1483+
1484+
#[test]
1485+
fn schedule_retry_fails_when_retry_target_block_is_full_named() {
1486+
schedule_retry_fails_when_retry_target_block_is_full(true);
1487+
}
1488+
14041489
/// Permanently overweight calls are not deleted but also not executed.
14051490
#[test]
14061491
fn scheduler_does_not_delete_permanently_overweight_call() {
@@ -2721,6 +2806,8 @@ fn scheduler_v3_named_cancel_named_works() {
27212806
.unwrap();
27222807
// Cancel the call by name.
27232808
assert_ok!(<Scheduler as Named<_, _, _>>::cancel_named(name));
2809+
// Lookup storage should be empty.
2810+
assert!(Lookup::<Test>::get(name).is_none());
27242811
// It did not get executed.
27252812
System::run_to_block::<AllPalletsWithSystem>(100);
27262813
assert!(logger::log().is_empty());
@@ -2751,6 +2838,8 @@ fn scheduler_v3_named_cancel_without_name_works() {
27512838
.unwrap();
27522839
// Cancel the call by address.
27532840
assert_ok!(<Scheduler as Anon<_, _, _>>::cancel(address));
2841+
// Address-cancellation of named task should still clear the lookup storage.
2842+
assert!(Lookup::<Test>::get(name).is_none());
27542843
// It did not get executed.
27552844
System::run_to_block::<AllPalletsWithSystem>(100);
27562845
assert!(logger::log().is_empty());

0 commit comments

Comments
 (0)