Skip to content

Commit 1ed522a

Browse files
fix: Check hook status after an upgrade/reinstall/uninstall/install (#3195)
Problem: @mraszyk pointed out [here](dfinity/portal#3761 (comment)): > The hook should not run after an upgrade/reinstall/uninstall/install if the condition is not satisfied after the upgrade (although the condition was satisfied before the upgrade and the hook did not execute yet). Solution: This PR should enforce that the status of the hook condition is updated each time we `upgrade/reinstall/uninstall/install` code.
1 parent 194648a commit 1ed522a

File tree

9 files changed

+930
-657
lines changed

9 files changed

+930
-657
lines changed

rs/execution_environment/src/execution_environment.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3070,7 +3070,7 @@ impl ExecutionEnvironment {
30703070
let execution_duration = since.elapsed().as_secs_f64();
30713071
match dts_result {
30723072
DtsInstallCodeResult::Finished {
3073-
canister,
3073+
mut canister,
30743074
mut message,
30753075
call_id,
30763076
instructions_used,
@@ -3108,6 +3108,7 @@ impl ExecutionEnvironment {
31083108
Err(err.into())
31093109
}
31103110
};
3111+
canister.update_on_low_wasm_memory_hook_condition();
31113112
state.put_canister_state(canister);
31123113
let refund = message.take_cycles();
31133114
// The message can be removed because a response was produced.

rs/execution_environment/src/execution_environment/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ fn get_canister_status_from_another_canister_when_memory_low() {
713713
let controller = test.universal_canister().unwrap();
714714
let binary = wat::parse_str("(module)").unwrap();
715715
let canister = test.create_canister(Cycles::new(1_000_000_000_000));
716-
let memory_allocation = NumBytes::from(158);
716+
let memory_allocation = NumBytes::from(300);
717717
test.install_canister_with_allocation(canister, binary, None, Some(memory_allocation.get()))
718718
.unwrap();
719719
let canister_status_args = Encode!(&CanisterIdRecord::from(canister)).unwrap();

rs/execution_environment/src/execution_environment/tests/canister_task.rs

+182-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use assert_matches::assert_matches;
22
use ic_config::{execution_environment::Config as HypervisorConfig, subnet_config::SubnetConfig};
33
use ic_error_types::RejectCode;
44
use ic_management_canister_types::{CanisterSettingsArgsBuilder, CanisterStatusType};
5+
use ic_management_canister_types::{CanisterUpgradeOptions, WasmMemoryPersistence};
56
use ic_registry_subnet_type::SubnetType;
67
use ic_replicated_state::canister_state::NextExecution;
78
use ic_replicated_state::canister_state::WASM_PAGE_SIZE_IN_BYTES;
@@ -823,6 +824,7 @@ fn global_timer_produces_transient_error_on_out_of_cycles() {
823824
fn get_wat_with_update_and_hook_mem_grow(
824825
update_grow_mem_size: i32,
825826
hook_grow_mem_size: i32,
827+
with_enchanced_ortogonal_persistence: bool,
826828
) -> String {
827829
let mut wat = r#"
828830
(module
@@ -843,7 +845,17 @@ fn get_wat_with_update_and_hook_mem_grow(
843845
wat.push_str(
844846
r#")))
845847
)
846-
(memory 1 20)
848+
(memory 1 20)"#,
849+
);
850+
if with_enchanced_ortogonal_persistence {
851+
wat.push_str(
852+
r#"
853+
(@custom "icp:private enhanced-orthogonal-persistence" "")
854+
"#,
855+
);
856+
}
857+
wat.push_str(
858+
r#"
847859
)"#,
848860
);
849861
wat
@@ -856,7 +868,8 @@ fn on_low_wasm_memory_is_executed() {
856868
let update_grow_mem_size = 7;
857869
let hook_grow_mem_size = 5;
858870

859-
let wat = get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size);
871+
let wat =
872+
get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size, false);
860873

861874
let canister_id = test.canister_from_wat(wat.as_str()).unwrap();
862875

@@ -905,7 +918,8 @@ fn on_low_wasm_memory_is_executed_before_message() {
905918
let update_grow_mem_size = 7;
906919
let hook_grow_mem_size = 5;
907920

908-
let wat = get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size);
921+
let wat =
922+
get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size, false);
909923

910924
let canister_id = test.canister_from_wat(wat.as_str()).unwrap();
911925

@@ -955,14 +969,178 @@ fn on_low_wasm_memory_is_executed_before_message() {
955969
);
956970
}
957971

972+
#[test]
973+
fn on_low_wasm_memory_is_executed_after_upgrade_if_condition_holds() {
974+
let mut test = ExecutionTestBuilder::new().with_manual_execution().build();
975+
976+
let update_grow_mem_size = 7;
977+
let hook_grow_mem_size = 5;
978+
979+
let wat: String =
980+
get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size, true);
981+
982+
let canister_id = test.canister_from_wat(wat.as_str()).unwrap();
983+
984+
test.canister_update_wasm_memory_limit_and_wasm_memory_threshold(
985+
canister_id,
986+
(20 * WASM_PAGE_SIZE_IN_BYTES as u64).into(),
987+
(15 * WASM_PAGE_SIZE_IN_BYTES as u64).into(),
988+
)
989+
.unwrap();
990+
991+
// Here we have:
992+
// wasm_capacity = wasm_memory_limit = 20 Wasm Pages
993+
// wasm_memory_threshold = 15 Wasm Pages
994+
995+
// Initially wasm_memory.size = 1
996+
assert_eq!(
997+
test.execution_state(canister_id).wasm_memory.size,
998+
NumWasmPages::new(1)
999+
);
1000+
1001+
test.ingress_raw(canister_id, "grow_mem", vec![]);
1002+
test.ingress_raw(canister_id, "grow_mem", vec![]);
1003+
1004+
// First ingress messages gets executed.
1005+
// wasm_memory.size = 1 + 7 = 8
1006+
// wasm_capacity - used_wasm_memory < self.wasm_memory_threshold
1007+
// Hook condition is triggered.
1008+
test.execute_slice(canister_id);
1009+
assert_eq!(
1010+
test.execution_state(canister_id).wasm_memory.size,
1011+
NumWasmPages::new(8)
1012+
);
1013+
1014+
let result = test.upgrade_canister_v2(
1015+
canister_id,
1016+
wat::parse_str(wat).unwrap(),
1017+
CanisterUpgradeOptions {
1018+
skip_pre_upgrade: None,
1019+
wasm_memory_persistence: Some(WasmMemoryPersistence::Keep),
1020+
},
1021+
);
1022+
assert_eq!(result, Ok(()));
1023+
1024+
// Upgrade is executed, and the wasm_memory size is unchanged.
1025+
// Hook condition is triggered.
1026+
assert_eq!(
1027+
test.execution_state(canister_id).wasm_memory.size,
1028+
NumWasmPages::new(8)
1029+
);
1030+
1031+
// Though we have the second ingress message awaiting to be processed,
1032+
// hook will be executed first.
1033+
test.execute_slice(canister_id);
1034+
assert_eq!(
1035+
test.execution_state(canister_id).wasm_memory.size,
1036+
NumWasmPages::new(13)
1037+
);
1038+
1039+
// The second ingress message is executed after the hook.
1040+
test.execute_slice(canister_id);
1041+
assert_eq!(
1042+
test.execution_state(canister_id).wasm_memory.size,
1043+
NumWasmPages::new(20)
1044+
);
1045+
}
1046+
1047+
#[test]
1048+
fn on_low_wasm_memory_is_not_executed_after_upgrade_if_condition_becomes_unsatisfied() {
1049+
let mut test = ExecutionTestBuilder::new().with_manual_execution().build();
1050+
1051+
let update_grow_mem_size = 3;
1052+
let hook_grow_mem_size = 5;
1053+
1054+
let wat: String =
1055+
get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size, false);
1056+
1057+
let canister_id = test.canister_from_wat(wat.as_str()).unwrap();
1058+
1059+
test.canister_update_wasm_memory_limit_and_wasm_memory_threshold(
1060+
canister_id,
1061+
(20 * WASM_PAGE_SIZE_IN_BYTES as u64).into(),
1062+
(15 * WASM_PAGE_SIZE_IN_BYTES as u64).into(),
1063+
)
1064+
.unwrap();
1065+
1066+
// Here we have:
1067+
// wasm_capacity = wasm_memory_limit = 20 Wasm Pages
1068+
// wasm_memory_threshold = 15 Wasm Pages
1069+
1070+
// Initially wasm_memory.size = 1
1071+
assert_eq!(
1072+
test.execution_state(canister_id).wasm_memory.size,
1073+
NumWasmPages::new(1)
1074+
);
1075+
1076+
test.ingress_raw(canister_id, "grow_mem", vec![]);
1077+
test.ingress_raw(canister_id, "grow_mem", vec![]);
1078+
test.ingress_raw(canister_id, "grow_mem", vec![]);
1079+
1080+
// First ingress messages gets executed.
1081+
// wasm_memory.size = 1 + 3 = 4
1082+
// wasm_capacity - used_wasm_memory > self.wasm_memory_threshold
1083+
// Hook condition is not triggered.
1084+
test.execute_slice(canister_id);
1085+
assert_eq!(
1086+
test.execution_state(canister_id).wasm_memory.size,
1087+
NumWasmPages::new(4)
1088+
);
1089+
1090+
// Second ingress messages gets executed.
1091+
// wasm_memory.size = 4 + 3 = 7
1092+
// wasm_capacity - used_wasm_memory < self.wasm_memory_threshold
1093+
// Hook condition is triggered.
1094+
test.execute_slice(canister_id);
1095+
assert_eq!(
1096+
test.execution_state(canister_id).wasm_memory.size,
1097+
NumWasmPages::new(7)
1098+
);
1099+
println!("canister upgrade");
1100+
1101+
let result = test.upgrade_canister_v2(
1102+
canister_id,
1103+
wat::parse_str(wat).unwrap(),
1104+
CanisterUpgradeOptions {
1105+
skip_pre_upgrade: None,
1106+
wasm_memory_persistence: None,
1107+
},
1108+
);
1109+
assert_eq!(result, Ok(()));
1110+
println!("canister upgrade");
1111+
1112+
// Upgrade is executed, and the wasm_memory size reset to 1.
1113+
// Hook condition is not triggered.
1114+
assert_eq!(
1115+
test.execution_state(canister_id).wasm_memory.size,
1116+
NumWasmPages::new(1)
1117+
);
1118+
1119+
// Though the hook was initially scheduled, it is now removed
1120+
// from queue, and the third ingress message will be executed.
1121+
test.execute_slice(canister_id);
1122+
assert_eq!(
1123+
test.execution_state(canister_id).wasm_memory.size,
1124+
NumWasmPages::new(4)
1125+
);
1126+
1127+
// There are no messages left to be executed.
1128+
test.execute_slice(canister_id);
1129+
assert_eq!(
1130+
test.execution_state(canister_id).wasm_memory.size,
1131+
NumWasmPages::new(4)
1132+
);
1133+
}
1134+
9581135
#[test]
9591136
fn on_low_wasm_memory_is_executed_once() {
9601137
let mut test = ExecutionTestBuilder::new().build();
9611138

9621139
let update_grow_mem_size = 7;
9631140
let hook_grow_mem_size = 2;
9641141

965-
let wat = get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size);
1142+
let wat =
1143+
get_wat_with_update_and_hook_mem_grow(update_grow_mem_size, hook_grow_mem_size, false);
9661144

9671145
let canister_id = test.canister_from_wat(wat.as_str()).unwrap();
9681146

rs/replicated_state/src/canister_state.rs

+13
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,13 @@ impl CanisterState {
375375
+ self.system_state.snapshots_memory_usage
376376
}
377377

378+
/// Returns the amount of Wasm memory currently used by the canister in bytes.
379+
pub fn wasm_memory_usage(&self) -> NumBytes {
380+
self.execution_state
381+
.as_ref()
382+
.map_or(NumBytes::from(0), |es| es.wasm_memory_usage())
383+
}
384+
378385
/// Returns the amount of execution memory (heap, stable, globals, Wasm)
379386
/// currently used by the canister in bytes.
380387
pub fn execution_memory_usage(&self) -> NumBytes {
@@ -564,6 +571,12 @@ impl CanisterState {
564571
.map_or(NumBytes::from(0), |es| es.heap_delta())
565572
+ self.system_state.wasm_chunk_store.heap_delta()
566573
}
574+
575+
/// Updates status of `OnLowWasmMemory` hook.
576+
pub fn update_on_low_wasm_memory_hook_condition(&mut self) {
577+
self.system_state
578+
.update_on_low_wasm_memory_hook_status(self.memory_usage(), self.wasm_memory_usage());
579+
}
567580
}
568581

569582
/// The result of `next_execution()` function.

rs/replicated_state/src/canister_state/execution_state.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -561,13 +561,18 @@ impl ExecutionState {
561561
self.exports.has_method(method)
562562
}
563563

564+
/// Returns the Wasm memory currently used by the `ExecutionState`.
565+
pub fn wasm_memory_usage(&self) -> NumBytes {
566+
num_bytes_try_from(self.wasm_memory.size)
567+
.expect("could not convert from wasm memory number of pages to bytes")
568+
}
569+
564570
/// Returns the memory currently used by the `ExecutionState`.
565571
pub fn memory_usage(&self) -> NumBytes {
566572
// We use 8 bytes per global.
567573
let globals_size_bytes = 8 * self.exported_globals.len() as u64;
568574
let wasm_binary_size_bytes = self.wasm_binary.binary.len() as u64;
569-
num_bytes_try_from(self.wasm_memory.size)
570-
.expect("could not convert from wasm memory number of pages to bytes")
575+
self.wasm_memory_usage()
571576
+ num_bytes_try_from(self.stable_memory.size)
572577
.expect("could not convert from stable memory number of pages to bytes")
573578
+ NumBytes::from(globals_size_bytes)

0 commit comments

Comments
 (0)