Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 2 additions & 1 deletion rs/execution_environment/src/canister_logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ic_management_canister_types_private::{
FetchCanisterLogsRequest, FetchCanisterLogsResponse, LogVisibilityV2,
};
use ic_replicated_state::CanisterState;
use ic_replicated_state::metadata_state::UnflushedCheckpointOps;
use ic_types::{NumBytes, NumInstructions, PrincipalId};

pub(crate) fn fetch_canister_logs(
Expand All @@ -24,7 +25,7 @@ pub(crate) fn fetch_canister_logs(
canister_id,
reply: Some(reply),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down
70 changes: 42 additions & 28 deletions rs/execution_environment/src/canister_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ use ic_replicated_state::canister_state::system_state::wasm_chunk_store::{
self, CHUNK_SIZE, ChunkValidationResult, WasmChunkHash, WasmChunkStore,
};
use ic_replicated_state::metadata_state::{
UnflushedCheckpointOp, subnet_call_context_manager::InstallCodeCallId,
UnflushedCheckpointOps, subnet_call_context_manager::InstallCodeCallId,
};
use ic_replicated_state::page_map::{Buffer, PageAllocatorFileDescriptor};
use ic_replicated_state::{
Expand Down Expand Up @@ -776,7 +776,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase,
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -1042,7 +1042,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: rejects,
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -1087,7 +1087,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply,
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -1121,7 +1121,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject,
Expand Down Expand Up @@ -1662,7 +1662,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -1694,7 +1694,7 @@ impl CanisterManager {
canister_id,
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -1809,7 +1809,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(reply.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -1857,7 +1857,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(reply.encode()),
heap_delta_increase: chunk_bytes,
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -1899,7 +1899,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -2117,9 +2117,13 @@ impl CanisterManager {
let new_snapshot =
CanisterSnapshot::from_canister(canister, time).map_err(CanisterManagerError::from)?;

// Delete old snapshot identified by `replace_snapshot`.
// Delete old snapshot identified by `replace_snapshot`, recording the deletion
// so that its directory is also deleted from the tip.
let mut unflushed_checkpoint_ops = UnflushedCheckpointOps::default();
if let Some(replace_snapshot) = replace_snapshot {
canister.canister_snapshots.remove(replace_snapshot);
canister
.canister_snapshots
.remove(replace_snapshot, &mut unflushed_checkpoint_ops);
}

let heap_delta = new_snapshot.heap_delta();
Expand All @@ -2129,6 +2133,7 @@ impl CanisterManager {
canister
.canister_snapshots
.push(snapshot_id, Arc::new(new_snapshot));
unflushed_checkpoint_ops.take_snapshot(canister_id, snapshot_id);

// Optionally uninstall the canister's code atomically after taking the
// snapshot, recording the corresponding `CanisterCodeUninstall` canister
Expand Down Expand Up @@ -2176,10 +2181,7 @@ impl CanisterManager {
canister_id,
reply: Some(reply.encode()),
heap_delta_increase: heap_delta,
unflushed_checkpoint_op: Some(UnflushedCheckpointOp::TakeSnapshot(
canister_id,
snapshot_id,
)),
unflushed_checkpoint_ops,
deleted_call_context_responses,
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -2553,14 +2555,17 @@ impl CanisterManager {
let heap_delta = new_canister.heap_delta();

*canister = new_canister;

// Record the load, so that the canister's files in the tip are replaced by the
// snapshot's.
let mut unflushed_checkpoint_ops = UnflushedCheckpointOps::default();
unflushed_checkpoint_ops.load_snapshot(canister_id, snapshot_id);

Ok(CanisterManagerResponse {
canister_id,
reply: Some(EmptyBlob.encode()),
heap_delta_increase: heap_delta,
unflushed_checkpoint_op: Some(UnflushedCheckpointOp::LoadSnapshot(
canister_id,
snapshot_id,
)),
unflushed_checkpoint_ops,
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -2627,13 +2632,18 @@ impl CanisterManager {
resource_saturation,
)?;

canister.canister_snapshots.remove(delete_snapshot_id);
// Delete the snapshot, recording the deletion so that its directory is also
// deleted from the tip.
let mut unflushed_checkpoint_ops = UnflushedCheckpointOps::default();
canister
.canister_snapshots
.remove(delete_snapshot_id, &mut unflushed_checkpoint_ops);

Ok(CanisterManagerResponse {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops,
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -2763,7 +2773,7 @@ impl CanisterManager {
canister_id,
reply: Some(reply.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -2853,9 +2863,13 @@ impl CanisterManager {
)?;
round_limits.instructions -= as_round_instructions(instructions);

// Delete old snapshot identified by `replace_snapshot`.
// Delete old snapshot identified by `replace_snapshot`, recording the deletion
// so that its directory is also deleted from the tip.
let mut unflushed_checkpoint_ops = UnflushedCheckpointOps::default();
if let Some(replace_snapshot) = args.replace_snapshot() {
canister.canister_snapshots.remove(replace_snapshot);
canister
.canister_snapshots
.remove(replace_snapshot, &mut unflushed_checkpoint_ops);
}

// Create new snapshot.
Expand All @@ -2879,7 +2893,7 @@ impl CanisterManager {
canister_id,
reply: Some(reply.encode()),
heap_delta_increase: heap_delta,
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops,
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -3001,7 +3015,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(0),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down Expand Up @@ -3044,7 +3058,7 @@ impl CanisterManager {
canister_id: canister.canister_id(),
reply: Some(EmptyBlob.encode()),
heap_delta_increase: NumBytes::new(bytes_written),
unflushed_checkpoint_op: None,
unflushed_checkpoint_ops: UnflushedCheckpointOps::default(),
deleted_call_context_responses: vec![],
stop_call_id_to_remove: None,
stop_contexts_to_reject: vec![],
Expand Down
6 changes: 3 additions & 3 deletions rs/execution_environment/src/canister_manager/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use ic_replicated_state::{
CanisterState,
canister_state::canister_snapshots::CanisterSnapshotError,
canister_state::system_state::wasm_chunk_store::{WasmChunkStore, chunk_size},
metadata_state::UnflushedCheckpointOp,
metadata_state::UnflushedCheckpointOps,
metadata_state::subnet_call_context_manager::InstallCodeCallId,
};
use ic_types::{
Expand Down Expand Up @@ -319,9 +319,9 @@ pub(crate) struct CanisterManagerResponse {
/// The heap delta increase produced by processing
/// the current request.
pub heap_delta_increase: NumBytes,
/// An unflushed checkpoint operation that must be handled
/// Unflushed checkpoint operations that must be handled
/// before the next checkpoint.
pub unflushed_checkpoint_op: Option<UnflushedCheckpointOp>,
pub unflushed_checkpoint_ops: UnflushedCheckpointOps,
/// (Reject) responses from call contexts that were marked as "deleted" while processing the current request.
/// Note. A call context is marked as "deleted" when a canister is uninstalled.
pub deleted_call_context_responses: Vec<Response>,
Expand Down
10 changes: 4 additions & 6 deletions rs/execution_environment/src/execution_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2147,12 +2147,10 @@ impl ExecutionEnvironment {
.heap_delta_debit
.saturating_add(&response.heap_delta_increase);
}
if let Some(unflushed_checkpoint_op) = response.unflushed_checkpoint_op {
state
.metadata
.unflushed_checkpoint_ops
.push(unflushed_checkpoint_op);
}
state
.metadata
.unflushed_checkpoint_ops
.extend(response.unflushed_checkpoint_ops);
if let Some(snapshot_id) = response.snapshot_to_make_immutable
&& let Some(canister) =
state.canister_state_make_mut(&snapshot_id.get_canister_id())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,91 @@ fn delete_canister_snapshot_succeeds() {
);
}

/// Tests that every way of deleting a snapshot records an
/// `UnflushedCheckpointOp::DeleteSnapshot`, so that the snapshot's directory is deleted
/// from the tip.
#[test]
fn snapshot_deletions_record_unflushed_checkpoint_ops() {
const CYCLES: Cycles = Cycles::new(1_000_000_000_000);

let mut test = ExecutionTestBuilder::new().build();
let canister_id = test
.canister_from_cycles_and_binary(CYCLES, UNIVERSAL_CANISTER_WASM.to_vec())
.unwrap();

// Installing a canister does not require any checkpoint ops.
assert!(test.state().metadata.unflushed_checkpoint_ops.is_empty());

// Taking a snapshot requires copying the canister's files to the snapshot's
// directory in the tip.
let (snapshot_id, _) = helper_take_snapshot(&mut test, canister_id);
assert_eq!(
test.state_mut().metadata.unflushed_checkpoint_ops.take(),
vec![UnflushedCheckpointOp::TakeSnapshot(
canister_id,
snapshot_id
)]
);

// Replacing the snapshot requires deleting the replaced snapshot's directory before
// creating the new one.
let args = TakeCanisterSnapshotArgs::new(canister_id, Some(snapshot_id), None, None);
let result = test.subnet_message("take_canister_snapshot", args.encode());
let response = CanisterSnapshotResponse::decode(&result.unwrap().bytes()).unwrap();
let new_snapshot_id = response.snapshot_id();
assert_eq!(
test.state_mut().metadata.unflushed_checkpoint_ops.take(),
vec![
UnflushedCheckpointOp::DeleteSnapshot(snapshot_id),
UnflushedCheckpointOp::TakeSnapshot(canister_id, new_snapshot_id),
]
);

// And so does explicitly deleting a snapshot.
let args = DeleteCanisterSnapshotArgs::new(canister_id, new_snapshot_id);
test.subnet_message("delete_canister_snapshot", args.encode())
.unwrap();
assert_eq!(
test.state_mut().metadata.unflushed_checkpoint_ops.take(),
vec![UnflushedCheckpointOp::DeleteSnapshot(new_snapshot_id)]
);
}

/// Tests that deleting a canister also records an
/// `UnflushedCheckpointOp::DeleteSnapshot` for each of its snapshots, so that their
/// directories are deleted from the tip along with the canister's.
#[test]
fn delete_canister_records_unflushed_checkpoint_ops_for_its_snapshots() {
const CYCLES: Cycles = Cycles::new(1_000_000_000_000);

let mut test = ExecutionTestBuilder::new().build();
let canister_id = test
.canister_from_cycles_and_binary(CYCLES, UNIVERSAL_CANISTER_WASM.to_vec())
.unwrap();

let (snapshot_id, _) = helper_take_snapshot(&mut test, canister_id);
assert_eq!(
test.state_mut().metadata.unflushed_checkpoint_ops.take(),
vec![UnflushedCheckpointOp::TakeSnapshot(
canister_id,
snapshot_id
)]
);

let _ = test.stop_canister(canister_id);
test.process_stopping_canisters();
test.delete_canister(canister_id).unwrap();

// Both the snapshot's and the canister's directories must be deleted from the tip.
assert_eq!(
test.state_mut().metadata.unflushed_checkpoint_ops.take(),
vec![
UnflushedCheckpointOp::DeleteSnapshot(snapshot_id),
UnflushedCheckpointOp::DeleteCanister(canister_id),
]
);
}

#[test]
fn list_canister_snapshot_fails_canister_not_found() {
let own_subnet = subnet_test_id(1);
Expand Down
Loading
Loading