diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ad5db1bd..231c12ea15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ # UNRELEASED +### feat: `dfx canister [create|update-settings] --wasm-memory-threshold` + +This adds support for the WASM memory threshold, used in conjunction with `--wasm-memory-limit`. +When the remaining memory until the limit falls below the threshold, the canister's +`on_low_wasm_memory` handler is run. + ### fix: `dfx deploy --by-proposal` no longer sends chunk data in ProposeCommitBatch Recently we made `dfx deploy` include some chunk data in CommitBatch, in order to streamline diff --git a/Cargo.lock b/Cargo.lock index ed6c78d48d..34d63ca273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,6 +303,12 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "argon2" version = "0.4.1" @@ -351,6 +357,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-io" version = "1.13.0" @@ -402,6 +419,15 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "async-watch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a078faf4e27c0c6cc0efb20e5da59dcccc04968ebf2801d8e0b2195124cdcdb2" +dependencies = [ + "event-listener 2.5.3", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1582,7 +1608,7 @@ dependencies = [ "ic-asset", "ic-cdk", "ic-identity-hsm", - "ic-utils 0.39.0", + "ic-utils 0.39.2", "ic-wasm", "icrc-ledger-types", "idl2json", @@ -1657,7 +1683,7 @@ dependencies = [ "humantime-serde", "ic-agent", "ic-identity-hsm", - "ic-utils 0.39.0", + "ic-utils 0.39.2", "itertools 0.10.5", "k256 0.11.6", "keyring", @@ -2732,12 +2758,14 @@ dependencies = [ [[package]] name = "ic-agent" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158138fcb769fe6288e63d5db221c904e472cfb7d376aba13a38c060f2984e63" +version = "0.39.2" +source = "git+https://github.com/dfinity/agent-rs?rev=0a51f2a65dde7d9e1790c378bd60e1768e3be257#0a51f2a65dde7d9e1790c378bd60e1768e3be257" dependencies = [ + "arc-swap", + "async-channel", "async-lock 3.4.0", "async-trait", + "async-watch", "backoff", "cached 0.52.0", "candid", @@ -2749,8 +2777,8 @@ dependencies = [ "hex", "http 1.2.0", "http-body 1.0.1", - "ic-certification 2.6.0", - "ic-transport-types 0.39.1", + "ic-certification 3.0.2", + "ic-transport-types 0.39.2", "ic-verify-bls-signature", "k256 0.13.4", "leb128", @@ -2767,7 +2795,8 @@ dependencies = [ "serde_repr", "sha2 0.10.8", "simple_asn1", - "thiserror 1.0.69", + "stop-token", + "thiserror 2.0.6", "time", "tokio", "tower-service", @@ -2789,7 +2818,7 @@ dependencies = [ "globset", "hex", "ic-agent", - "ic-utils 0.39.0", + "ic-utils 0.39.2", "itertools 0.10.5", "json5", "mime", @@ -2933,6 +2962,18 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "ic-certification" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eae40f26fcac9c141cad54d9aa5f423efffde78ac371057c53d275ebbcad443" +dependencies = [ + "hex", + "serde", + "serde_bytes", + "sha2 0.10.8", +] + [[package]] name = "ic-certification-testing" version = "2.3.0" @@ -3176,16 +3217,15 @@ dependencies = [ [[package]] name = "ic-identity-hsm" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8722411845f0a4b2c526b906049d45f87c62c78ddf1c38ecdc03ecbd34ffc1d6" +version = "0.39.2" +source = "git+https://github.com/dfinity/agent-rs?rev=0a51f2a65dde7d9e1790c378bd60e1768e3be257#0a51f2a65dde7d9e1790c378bd60e1768e3be257" dependencies = [ "hex", "ic-agent", "pkcs11", "sha2 0.10.8", "simple_asn1", - "thiserror 1.0.69", + "thiserror 2.0.6", ] [[package]] @@ -3294,20 +3334,19 @@ dependencies = [ [[package]] name = "ic-transport-types" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8789a5c176bb1b925fa58ca97c651a3995d504e76101e93d2a17f558bdcf66" +version = "0.39.2" +source = "git+https://github.com/dfinity/agent-rs?rev=0a51f2a65dde7d9e1790c378bd60e1768e3be257#0a51f2a65dde7d9e1790c378bd60e1768e3be257" dependencies = [ "candid", "hex", - "ic-certification 2.6.0", + "ic-certification 3.0.2", "leb128", "serde", "serde_bytes", "serde_cbor", "serde_repr", "sha2 0.10.8", - "thiserror 1.0.69", + "thiserror 2.0.6", ] [[package]] @@ -3365,9 +3404,8 @@ dependencies = [ [[package]] name = "ic-utils" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1da4a68c45146018b8496c157ad94126b9c202ab4400c6c0a9030c1ef0f0ba" +version = "0.39.2" +source = "git+https://github.com/dfinity/agent-rs?rev=0a51f2a65dde7d9e1790c378bd60e1768e3be257#0a51f2a65dde7d9e1790c378bd60e1768e3be257" dependencies = [ "async-trait", "candid", @@ -3380,7 +3418,7 @@ dependencies = [ "sha2 0.10.8", "strum 0.26.3", "strum_macros 0.26.4", - "thiserror 1.0.69", + "thiserror 2.0.6", "time", "tokio", ] @@ -3617,7 +3655,7 @@ dependencies = [ "humantime", "ic-agent", "ic-asset", - "ic-utils 0.39.0", + "ic-utils 0.39.2", "libflate 1.4.0", "num-traits", "pem 1.1.1", @@ -6081,6 +6119,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stop-token" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af91f480ee899ab2d9f8435bfdfc14d08a5754bd9d3fef1f1a1c23336aad6c8b" +dependencies = [ + "async-channel", + "cfg-if", + "futures-core", + "pin-project-lite", +] + [[package]] name = "string_cache" version = "0.8.7" diff --git a/Cargo.toml b/Cargo.toml index 6b3e137c27..58bbde2814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,11 +22,11 @@ license = "Apache-2.0" candid = "0.10.11" candid_parser = "0.1.4" dfx-core = { path = "src/dfx-core", version = "0.1.0" } -ic-agent = "0.39" +ic-agent = { version = "0.39", git = "https://github.com/dfinity/agent-rs", rev = "0a51f2a65dde7d9e1790c378bd60e1768e3be257" } ic-asset = { path = "src/canisters/frontend/ic-asset", version = "0.21.0" } ic-cdk = "0.13.1" -ic-identity-hsm = "0.39" -ic-utils = "0.39" +ic-identity-hsm = { version = "0.39", git = "https://github.com/dfinity/agent-rs", rev = "0a51f2a65dde7d9e1790c378bd60e1768e3be257" } +ic-utils = { version = "0.39", git = "https://github.com/dfinity/agent-rs", rev = "0a51f2a65dde7d9e1790c378bd60e1768e3be257" } aes-gcm = "0.10.3" anyhow = "1.0.56" diff --git a/docs/cli-reference/dfx-canister.mdx b/docs/cli-reference/dfx-canister.mdx index 5b6f7d3b8d..301d26ec93 100644 --- a/docs/cli-reference/dfx-canister.mdx +++ b/docs/cli-reference/dfx-canister.mdx @@ -280,6 +280,7 @@ You can use the following options with the `dfx canister create` command. | `--memory-allocation ` | Specifies how much memory the canister is allowed to use in total. This should be a value in the range [0..12 GiB]. A setting of 0 means the canister will have access to memory on a “best-effort” basis: It will only be charged for the memory it uses, but at any point in time may stop running if it tries to allocate more memory when there isn’t space available on the subnet. | | `--reserved-cycles-limit ` | Specifies the upper limit for the canister's reserved cycles. | | `--wasm-memory-limit ` | Specifies a soft upper limit for the canister's heap memory. | +| `--wasm-memory-threshold ` | Specifies a threshold remaining amount of memory before the canister's low-memory hook runs. | | `--log-viewer ` | Specifies the principal as an allowed viewers. Can be specified more than once. Cannot be used with `--log-visibility`. | | `--log-visibility ` | Specifies who can read the canister's logs: "controllers" or "public". For custom allowed viewers, use `--log-viewer`. | | `--no-wallet` | Performs the call with the user Identity as the Sender of messages. Bypasses the Wallet canister. Enabled by default. | @@ -1150,6 +1151,7 @@ You can specify the following options for the `dfx canister update-settings` com | `--remove-controller ` | Removes a principal from the list of controllers of the canister. | | `--remove-log-viewer ` | Removes a principal from the list of log viewers of the canister. Can be specified more than once to remove multiple log viewers. | | `--freezing-threshold ` | Set the [freezing threshold](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister) in seconds for a canister. This should be a value in the range [0..2^64^-1]. Very long thresholds require the `--confirm-very-long-freezing-threshold` option. | +| `--wasm-memory-threshold ` | Specifies a threshold remaining amount of memory before the canister's low-memory hook runs. | | `-y`, `--yes` | Skips yes/no checks by answering 'yes'. Such checks can result in loss of control, so this is not recommended outside of CI. | ### Arguments diff --git a/docs/dfx-json-schema.json b/docs/dfx-json-schema.json index aa98486369..b49c3a3b6a 100644 --- a/docs/dfx-json-schema.json +++ b/docs/dfx-json-schema.json @@ -447,7 +447,8 @@ "log_visibility": null, "memory_allocation": null, "reserved_cycles_limit": null, - "wasm_memory_limit": null + "wasm_memory_limit": null, + "wasm_memory_threshold": null }, "allOf": [ { @@ -1020,6 +1021,19 @@ "type": "null" } ] + }, + "wasm_memory_threshold": { + "title": "Wasm Memory Threshold", + "description": "Specifies a threshold (in bytes) on the Wasm memory usage of the canister, as a distance from `wasm_memory_limit`.\n\nWhen the remaining memory before the limit drops below this threshold, its `on_low_wasm_memory` hook will be invoked. This enables it to self-optimize, or raise an alert, or otherwise attempt to prevent itself from reaching `wasm_memory_limit`.\n\nMust be a number of bytes between 0 and 2^48 (i.e. 256 TiB), inclusive. Can be specified as an integer, or as an SI unit string (e.g. \"4KB\", \"2 MiB\")", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/Byte" + }, + { + "type": "null" + } + ] } } }, diff --git a/e2e/assets/allocate_memory/src/e2e_project_backend/src/lib.rs b/e2e/assets/allocate_memory/src/e2e_project_backend/src/lib.rs index 7ea906db17..3d9a93c679 100644 --- a/e2e/assets/allocate_memory/src/e2e_project_backend/src/lib.rs +++ b/e2e/assets/allocate_memory/src/e2e_project_backend/src/lib.rs @@ -7,3 +7,8 @@ fn greet(s: String) -> String { fn greet_update(s: String) -> String { format!("Hello, {s}!") } + +#[ic_cdk::on_low_wasm_memory] +fn on_low_wasm_memory() { + ic_cdk::println!("Low memory!"); +} diff --git a/e2e/tests-dfx/update_settings.bash b/e2e/tests-dfx/update_settings.bash index 74d668cf61..c96b82e663 100644 --- a/e2e/tests-dfx/update_settings.bash +++ b/e2e/tests-dfx/update_settings.bash @@ -61,6 +61,23 @@ teardown() { assert_contains "Canister exceeded its current Wasm memory limit of 8 bytes" } +@test "set wasm memory threshold" { + dfx_new_rust + install_asset allocate_memory + dfx_start + assert_command dfx canister create e2e_project_backend --no-wallet --wasm-memory-threshold 2MiB --wasm-memory-limit 2MiB + assert_command dfx deploy e2e_project_backend + assert_command dfx canister status e2e_project_backend + assert_contains "Wasm memory threshold: 2_097_152 Bytes" + assert_command dfx canister update-settings e2e_project_backend --wasm-memory-threshold 1MiB + assert_command dfx canister status e2e_project_backend + assert_contains "Wasm memory threshold: 1_048_576 Bytes" + assert_command dfx canister call e2e_project_backend greet_update '("alice")' + sleep 1 + assert_command dfx canister logs e2e_project_backend + assert_contains "Low memory!" +} + @test "set log visibility" { dfx_new dfx_start diff --git a/src/dfx-core/src/config/model/dfinity.rs b/src/dfx-core/src/config/model/dfinity.rs index e26edada97..e05b1ecfd5 100644 --- a/src/dfx-core/src/config/model/dfinity.rs +++ b/src/dfx-core/src/config/model/dfinity.rs @@ -16,11 +16,13 @@ use crate::error::dfx_config::GetRemoteCanisterIdError::GetRemoteCanisterIdFaile use crate::error::dfx_config::GetReservedCyclesLimitError::GetReservedCyclesLimitFailed; use crate::error::dfx_config::GetSpecifiedIdError::GetSpecifiedIdFailed; use crate::error::dfx_config::GetWasmMemoryLimitError::GetWasmMemoryLimitFailed; +use crate::error::dfx_config::GetWasmMemoryThresholdError::GetWasmMemoryThresholdFailed; use crate::error::dfx_config::{ AddDependenciesError, GetCanisterConfigError, GetCanisterNamesWithDependenciesError, GetComputeAllocationError, GetFreezingThresholdError, GetLogVisibilityError, GetMemoryAllocationError, GetPullCanistersError, GetRemoteCanisterIdError, GetReservedCyclesLimitError, GetSpecifiedIdError, GetWasmMemoryLimitError, + GetWasmMemoryThresholdError, }; use crate::error::fs::CanonicalizePathError; use crate::error::load_dfx_config::LoadDfxConfigError; @@ -477,6 +479,21 @@ pub struct InitializationValues { #[schemars(with = "Option")] pub wasm_memory_limit: Option, + /// # Wasm Memory Threshold + /// + /// Specifies a threshold (in bytes) on the Wasm memory usage of the canister, + /// as a distance from `wasm_memory_limit`. + /// + /// When the remaining memory before the limit drops below this threshold, its + /// `on_low_wasm_memory` hook will be invoked. This enables it to self-optimize, + /// or raise an alert, or otherwise attempt to prevent itself from reaching + /// `wasm_memory_limit`. + /// + /// Must be a number of bytes between 0 and 2^48 (i.e. 256 TiB), inclusive. + /// Can be specified as an integer, or as an SI unit string (e.g. "4KB", "2 MiB") + #[schemars(with = "Option")] + pub wasm_memory_threshold: Option, + /// # Log Visibility /// Specifies who is allowed to read the canister's logs. /// @@ -1004,6 +1021,17 @@ impl ConfigInterface { .wasm_memory_limit) } + pub fn get_wasm_memory_threshold( + &self, + canister_name: &str, + ) -> Result, GetWasmMemoryThresholdError> { + Ok(self + .get_canister_config(canister_name) + .map_err(|e| GetWasmMemoryThresholdFailed(canister_name.to_string(), e))? + .initialization_values + .wasm_memory_threshold) + } + pub fn get_log_visibility( &self, canister_name: &str, diff --git a/src/dfx-core/src/error/dfx_config.rs b/src/dfx-core/src/error/dfx_config.rs index fa2e59c051..dabd5aea76 100644 --- a/src/dfx-core/src/error/dfx_config.rs +++ b/src/dfx-core/src/error/dfx_config.rs @@ -58,6 +58,12 @@ pub enum GetWasmMemoryLimitError { GetWasmMemoryLimitFailed(String, #[source] GetCanisterConfigError), } +#[derive(Error, Debug)] +pub enum GetWasmMemoryThresholdError { + #[error("Failed to get Wasm memory threshold for canister '{0}'")] + GetWasmMemoryThresholdFailed(String, #[source] GetCanisterConfigError), +} + #[derive(Error, Debug)] pub enum GetLogVisibilityError { #[error("Failed to get log visibility for canister '{0}'")] diff --git a/src/dfx/assets/project_templates/rust/src/__backend_name__/Cargo.toml b/src/dfx/assets/project_templates/rust/src/__backend_name__/Cargo.toml index 413f5fa468..f0a6ce3715 100644 --- a/src/dfx/assets/project_templates/rust/src/__backend_name__/Cargo.toml +++ b/src/dfx/assets/project_templates/rust/src/__backend_name__/Cargo.toml @@ -10,5 +10,5 @@ crate-type = ["cdylib"] [dependencies] candid = "0.10" -ic-cdk = "0.16" -ic-cdk-timers = "0.10" # Feel free to remove this dependency if you don't need timers +ic-cdk = "0.17" +ic-cdk-timers = "0.11" # Feel free to remove this dependency if you don't need timers diff --git a/src/dfx/src/commands/canister/call.rs b/src/dfx/src/commands/canister/call.rs index 1a740f7307..aecf4d9a56 100644 --- a/src/dfx/src/commands/canister/call.rs +++ b/src/dfx/src/commands/canister/call.rs @@ -145,6 +145,7 @@ pub fn get_effective_canister_id( | MgmtMethod::BitcoinGetUtxos | MgmtMethod::BitcoinSendTransaction | MgmtMethod::BitcoinGetCurrentFeePercentiles + | MgmtMethod::BitcoinGetBlockHeaders | MgmtMethod::EcdsaPublicKey | MgmtMethod::SignWithEcdsa | MgmtMethod::NodeMetricsHistory => Ok(CanisterId::management_canister()), diff --git a/src/dfx/src/commands/canister/create.rs b/src/dfx/src/commands/canister/create.rs index 368ee604f5..a0e5a610c1 100644 --- a/src/dfx/src/commands/canister/create.rs +++ b/src/dfx/src/commands/canister/create.rs @@ -4,7 +4,7 @@ use crate::lib::environment::Environment; use crate::lib::error::{DfxError, DfxResult}; use crate::lib::ic_attributes::{ get_compute_allocation, get_freezing_threshold, get_log_visibility, get_memory_allocation, - get_reserved_cycles_limit, get_wasm_memory_limit, CanisterSettings, + get_reserved_cycles_limit, get_wasm_memory_limit, get_wasm_memory_threshold, CanisterSettings, }; use crate::lib::operations::canister::{create_canister, skip_remote_canister}; use crate::lib::root_key::fetch_root_key_if_needed; @@ -92,6 +92,18 @@ pub struct CanisterCreateOpts { #[arg(long, value_parser = wasm_memory_limit_parser, hide = true)] wasm_memory_limit: Option, + /// Specifies a threshold (in bytes) on the Wasm memory usage of the canister, + /// as a distance from `wasm_memory_limit`. + /// + /// When the remaining memory before the limit drops below this threshold, its + /// `on_low_wasm_memory` hook will be invoked. This enables it to self-optimize, + /// or raise an alert, or otherwise attempt to prevent itself from reaching + /// `wasm_memory_limit`. + /// + /// Must be a number between 0 B and 256 TiB, inclusive. Can include units, e.g. "4KiB". + #[arg(long, value_parser = wasm_memory_limit_parser)] + wasm_memory_threshold: Option, + /// Specifies who is allowed to read the canister's logs. /// Can be either "controllers" or "public". #[arg(long, value_parser = log_visibility_parser, conflicts_with("log_viewer"))] @@ -208,6 +220,12 @@ pub async fn exec( Some(canister_name), ) .with_context(|| format!("Failed to read Wasm memory limit of {canister_name}."))?; + let wasm_memory_threshold = get_wasm_memory_threshold( + opts.wasm_memory_threshold, + Some(config_interface), + Some(canister_name), + ) + .with_context(|| format!("Failed to read Wasm memory threshold of {canister_name}."))?; let log_visibility = get_log_visibility( env, LogVisibilityOpt::from(&opts.log_visibility, &opts.log_viewer).as_ref(), @@ -231,6 +249,7 @@ pub async fn exec( freezing_threshold, reserved_cycles_limit, wasm_memory_limit, + wasm_memory_threshold, log_visibility, }, opts.created_at_time, @@ -285,6 +304,14 @@ pub async fn exec( Some(canister_name), ) .with_context(|| format!("Failed to read Wasm memory limit of {canister_name}."))?; + let wasm_memory_threshold = get_wasm_memory_threshold( + opts.wasm_memory_threshold, + Some(config_interface), + Some(canister_name), + ) + .with_context(|| { + format!("Failed to read Wasm memory threshold of {canister_name}.") + })?; let log_visibility = get_log_visibility( env, LogVisibilityOpt::from(&opts.log_visibility, &opts.log_viewer).as_ref(), @@ -308,6 +335,7 @@ pub async fn exec( freezing_threshold, reserved_cycles_limit, wasm_memory_limit, + wasm_memory_threshold, log_visibility, }, opts.created_at_time, diff --git a/src/dfx/src/commands/canister/delete.rs b/src/dfx/src/commands/canister/delete.rs index 02d3dcb30f..c99c5df90d 100644 --- a/src/dfx/src/commands/canister/delete.rs +++ b/src/dfx/src/commands/canister/delete.rs @@ -189,6 +189,7 @@ async fn delete_canister( freezing_threshold: Some(FreezingThreshold::try_from(0u8).unwrap()), reserved_cycles_limit: None, wasm_memory_limit: None, + wasm_memory_threshold: None, log_visibility: None, }; info!(log, "Setting the controller to identity principal."); diff --git a/src/dfx/src/commands/canister/status.rs b/src/dfx/src/commands/canister/status.rs index ecd8d7773d..9218774219 100644 --- a/src/dfx/src/commands/canister/status.rs +++ b/src/dfx/src/commands/canister/status.rs @@ -52,6 +52,11 @@ async fn canister_status( } else { "Not Set".to_string() }; + let wasm_memory_threshold = if let Some(threshold) = status.settings.wasm_memory_threshold { + format!("{} Bytes", threshold) + } else { + "Not Set".to_string() + }; let log_visibility = match status.settings.log_visibility { LogVisibility::Controllers => "controllers".to_string(), LogVisibility::Public => "public".to_string(), @@ -66,7 +71,27 @@ async fn canister_status( } }; - println!("Canister status call result for {canister}.\nStatus: {status}\nControllers: {controllers}\nMemory allocation: {memory_allocation}\nCompute allocation: {compute_allocation}\nFreezing threshold: {freezing_threshold}\nIdle cycles burned per day: {idle_cycles_burned_per_day}\nMemory Size: {memory_size:?}\nBalance: {balance} Cycles\nReserved: {reserved} Cycles\nReserved cycles limit: {reserved_cycles_limit}\nWasm memory limit: {wasm_memory_limit}\nModule hash: {module_hash}\nNumber of queries: {queries_total}\nInstructions spent in queries: {query_instructions_total}\nTotal query request payload size (bytes): {query_req_payload_total}\nTotal query response payload size (bytes): {query_resp_payload_total}\nLog visibility: {log_visibility}", + println!( + "\ +Canister status call result for {canister}. +Status: {status} +Controllers: {controllers} +Memory allocation: {memory_allocation} +Compute allocation: {compute_allocation} +Freezing threshold: {freezing_threshold} +Idle cycles burned per day: {idle_cycles_burned_per_day} +Memory Size: {memory_size:?} +Balance: {balance} Cycles +Reserved: {reserved} Cycles +Reserved cycles limit: {reserved_cycles_limit} +Wasm memory limit: {wasm_memory_limit} +Wasm memory threshold: {wasm_memory_threshold} +Module hash: {module_hash} +Number of queries: {queries_total} +Instructions spent in queries: {query_instructions_total} +Total query request payload size (bytes): {query_req_payload_total} +Total query response payload size (bytes): {query_resp_payload_total} +Log visibility: {log_visibility}", status = status.status, controllers = controllers.join(" "), memory_allocation = status.settings.memory_allocation, @@ -76,7 +101,9 @@ async fn canister_status( memory_size = status.memory_size, balance = status.cycles, reserved = status.reserved_cycles, - module_hash = status.module_hash.map_or_else(|| "None".to_string(), |v| format!("0x{}", hex::encode(v))), + module_hash = status + .module_hash + .map_or_else(|| "None".to_string(), |v| format!("0x{}", hex::encode(v))), queries_total = status.query_stats.num_calls_total, query_instructions_total = status.query_stats.num_instructions_total, query_req_payload_total = status.query_stats.request_payload_bytes_total, diff --git a/src/dfx/src/commands/canister/update_settings.rs b/src/dfx/src/commands/canister/update_settings.rs index db3a3184eb..1d18cb1555 100644 --- a/src/dfx/src/commands/canister/update_settings.rs +++ b/src/dfx/src/commands/canister/update_settings.rs @@ -4,7 +4,7 @@ use crate::lib::environment::Environment; use crate::lib::error::{DfxError, DfxResult}; use crate::lib::ic_attributes::{ get_compute_allocation, get_freezing_threshold, get_log_visibility, get_memory_allocation, - get_reserved_cycles_limit, get_wasm_memory_limit, CanisterSettings, + get_reserved_cycles_limit, get_wasm_memory_limit, get_wasm_memory_threshold, CanisterSettings, }; use crate::lib::operations::canister::{ get_canister_status, skip_remote_canister, update_settings, @@ -91,6 +91,18 @@ pub struct UpdateSettingsOpts { #[arg(long, value_parser = wasm_memory_limit_parser)] wasm_memory_limit: Option, + /// Specifies a threshold (in bytes) on the Wasm memory usage of the canister, + /// as a distance from `wasm_memory_limit`. + /// + /// When the remaining memory before the limit drops below this threshold, its + /// `on_low_wasm_memory` hook will be invoked. This enables it to self-optimize, + /// or raise an alert, or otherwise attempt to prevent itself from reaching + /// `wasm_memory_limit`. + /// + /// Must be a number between 0 B and 256 TiB, inclusive. Can include units, e.g. "4KiB". + #[arg(long, value_parser = wasm_memory_limit_parser)] + wasm_memory_threshold: Option, + #[command(flatten)] log_visibility_opt: Option, @@ -158,6 +170,8 @@ pub async fn exec( get_reserved_cycles_limit(opts.reserved_cycles_limit, config_interface, canister_name)?; let wasm_memory_limit = get_wasm_memory_limit(opts.wasm_memory_limit, config_interface, canister_name)?; + let wasm_memory_threshold = + get_wasm_memory_threshold(opts.wasm_memory_threshold, config_interface, canister_name)?; let mut current_status: Option = None; if let Some(log_visibility) = &opts.log_visibility_opt { if log_visibility.require_current_settings() { @@ -214,6 +228,7 @@ pub async fn exec( freezing_threshold, reserved_cycles_limit, wasm_memory_limit, + wasm_memory_threshold, log_visibility, }; update_settings(env, canister_id, settings, call_sender).await?; @@ -266,6 +281,14 @@ pub async fn exec( Some(canister_name), ) .with_context(|| format!("Failed to get Wasm memory limit for {canister_name}."))?; + let wasm_memory_threshold = get_wasm_memory_threshold( + opts.wasm_memory_threshold, + Some(config_interface), + Some(canister_name), + ) + .with_context(|| { + format!("Failed to get Wasm memory threshold for {canister_name}.") + })?; let mut current_status: Option = None; if let Some(log_visibility) = &opts.log_visibility_opt { if log_visibility.require_current_settings() { @@ -325,6 +348,7 @@ pub async fn exec( freezing_threshold, reserved_cycles_limit, wasm_memory_limit, + wasm_memory_threshold, log_visibility, }; update_settings(env, canister_id, settings, call_sender).await?; diff --git a/src/dfx/src/lib/ic_attributes/mod.rs b/src/dfx/src/lib/ic_attributes/mod.rs index a7cf379a02..f287132866 100644 --- a/src/dfx/src/lib/ic_attributes/mod.rs +++ b/src/dfx/src/lib/ic_attributes/mod.rs @@ -22,6 +22,7 @@ pub struct CanisterSettings { pub freezing_threshold: Option, pub reserved_cycles_limit: Option, pub wasm_memory_limit: Option, + pub wasm_memory_threshold: Option, pub log_visibility: Option, } @@ -51,6 +52,10 @@ impl From .wasm_memory_limit .map(u64::from) .map(candid::Nat::from), + wasm_memory_threshold: value + .wasm_memory_threshold + .map(u64::from) + .map(candid::Nat::from), log_visibility: value.log_visibility, } } @@ -107,6 +112,14 @@ impl TryFrom, + config_interface: Option<&ConfigInterface>, + canister_name: Option<&str>, +) -> DfxResult> { + let wasm_memory_threshold = match (wasm_memory_threshold, config_interface, canister_name) { + (Some(memory_threshold), _, _) => Some(memory_threshold), + (None, Some(config_interface), Some(canister_name)) => { + config_interface.get_wasm_memory_threshold(canister_name)? + } + _ => None, + }; + wasm_memory_threshold + .map(|arg| { + u64::try_from(arg.get_bytes()) + .map_err(|e| anyhow!(e)) + .and_then(|n| Ok(WasmMemoryLimit::try_from(n)?)) + .context("Wasm memory limit must be between 0 and 2^48 (i.e 256TB), inclusively.") + }) + .transpose() +} pub fn get_log_visibility( env: &dyn Environment, diff --git a/src/dfx/src/lib/migrate.rs b/src/dfx/src/lib/migrate.rs index f9ceda72a4..5a3c584952 100644 --- a/src/dfx/src/lib/migrate.rs +++ b/src/dfx/src/lib/migrate.rs @@ -120,6 +120,7 @@ async fn migrate_canister( memory_allocation: None, reserved_cycles_limit: None, wasm_memory_limit: None, + wasm_memory_threshold: None, log_visibility: None, }, },)), diff --git a/src/dfx/src/lib/operations/canister/create_canister.rs b/src/dfx/src/lib/operations/canister/create_canister.rs index 8c4952afc7..03c1e3928f 100644 --- a/src/dfx/src/lib/operations/canister/create_canister.rs +++ b/src/dfx/src/lib/operations/canister/create_canister.rs @@ -216,6 +216,7 @@ async fn create_with_management_canister( .with_optional_freezing_threshold(settings.freezing_threshold) .with_optional_reserved_cycles_limit(settings.reserved_cycles_limit) .with_optional_wasm_memory_limit(settings.wasm_memory_limit) + .with_optional_wasm_memory_threshold(settings.wasm_memory_threshold) .with_optional_log_visibility(settings.log_visibility) .await; const NEEDS_WALLET: &str = "In order to create a canister on this network, you must use a wallet in order to allocate cycles to the new canister. \ diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index b1bdff2a3a..b3689e7722 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -277,6 +277,7 @@ async fn register_canisters( freezing_threshold, reserved_cycles_limit, wasm_memory_limit, + wasm_memory_threshold: None, log_visibility, }, created_at_time, diff --git a/src/dfx/src/lib/operations/cmc.rs b/src/dfx/src/lib/operations/cmc.rs index 947a08649e..9194177eb4 100644 --- a/src/dfx/src/lib/operations/cmc.rs +++ b/src/dfx/src/lib/operations/cmc.rs @@ -75,6 +75,7 @@ pub async fn notify_create( freezing_threshold: None, reserved_cycles_limit: None, wasm_memory_limit: None, + wasm_memory_threshold: None, log_visibility: None, }) })