Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: on low wasm memory hook #3761

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

mraszyk
Copy link
Contributor

@mraszyk mraszyk commented Nov 15, 2024

This proposal is based on this forum post and has already been approved by motion proposal 106146.

Canister developers have to actively monitor their canisters to know if they are low on wasm memory. If detected too late, a canister can be completely stuck and forever un-upgradable.

To address this, we introduce a hook called on_low_wasm_memory. When defined, it is triggered whenever the canister's memory usage exceeds some developer-defined threshold.

@mraszyk
Copy link
Contributor Author

mraszyk commented Nov 15, 2024

If the canister gets frozen immediately after the function is scheduled for execution, the function will run once the canister's unfrozen if the canister's wasm memory remains above the threshold t.

This property is not established by the implementation.

@mraszyk
Copy link
Contributor Author

mraszyk commented Nov 15, 2024

The implementation only considers stable memory and ignores, e.g., the chunk store and snapshot size, when deriving the wasm memory capacity.

@mraszyk
Copy link
Contributor Author

mraszyk commented Nov 15, 2024

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).

Copy link

github-actions bot commented Nov 15, 2024

🤖 Here's your preview: https://xaml7-fiaaa-aaaam-abdka-cai.icp0.io

@mraszyk mraszyk added the interface-spec Changes to the IC Interface Specification label Nov 18, 2024
@dragoljub-duric
Copy link

Regarding the point:
"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)."
Because the hook condition is checked whenever the canister tries to grow wasm memory, can it ever happen that the canister does not try to grow wasm memory when running upgrade/reinstall/uninstall/install?

@mraszyk
Copy link
Contributor Author

mraszyk commented Nov 26, 2024

Because the hook condition is checked whenever the canister tries to grow wasm memory, can it ever happen that the canister does not try to grow wasm memory when running upgrade/reinstall/uninstall/install?

Yes, e.g., if the canister does not have any pre- and post-upgrade hooks (in which case there's no wasm execution during canister installation).

@Dfinity-Bjoern Dfinity-Bjoern marked this pull request as ready for review December 10, 2024 14:32
@Dfinity-Bjoern Dfinity-Bjoern requested a review from a team as a code owner December 10, 2024 14:32
@Dfinity-Bjoern Dfinity-Bjoern changed the title feat: on low wasm memory hook [FINAL] feat: on low wasm memory hook Dec 10, 2024
github-merge-queue bot pushed a commit to dfinity/ic that referenced this pull request Dec 18, 2024
…#2864)

To support `on_low_wasm_memory` hook we are adding
`wasm_memory_threshold` to `NNS` so it allows users to update its value
using `NNS` proposal. We are as well adding `wasm_memory_threshold` to
canister settings to canisters in `IC` repo.

The `wasm_memory_threshold` will be added to the interface specification
in dfinity/portal#3761

---------

Co-authored-by: Andre Popovitch <[email protected]>
Co-authored-by: Arshavir Ter-Gabrielyan <[email protected]>
github-merge-queue bot pushed a commit to dfinity/ic that referenced this pull request Jan 8, 2025
…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.
github-merge-queue bot pushed a commit to dfinity/ic that referenced this pull request Jan 8, 2025
…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.
:::note

While the above function is scheduled immediately once the condition above is triggered, it may not necessarily be executed immediately if the canister does not have enough cycles.
If the canister gets frozen immediately after the function is scheduled for execution, the function will run once the canister's unfrozen _if_ the canister's wasm memory remains above the threshold `t`.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should indeed be rephrased, but I'd suggest: if the canister's remaining wasm memory size in bytes remains below the threshold t.

@dragoljub-duric
Copy link

dragoljub-duric commented Jan 15, 2025

In slack conversation:

Luc Bläser
Scenario:
Canister sets memory threshold at 1 GB.
Canister free memory is 0.99 GB -> hook is triggered
Now, canister sets memory threshold to 0.5 GB, free memory is still 0.99 GB
t1 > t remaining Wasm memory is greater than the Wasm memory threshold
And now canister allocates more memory, until 0.49 GB free memory
t2 > t1 remaining wasm memory falls below Wasm memory threshold
Shouldn't the hook now be retriggered?

Dragoljub Duric
That is a very good example. And I think that case may be missing. The reason is to check for the hook condition to be added after every memory_grow operation or after install/reinstall/uninstall/upgrade of the canister. But in this specific case, when memory is 0.99 GB hook will be executed and the status will be Executed since we do not have to check for the hook condition when we set a new threshold the status will remain Executed. Finally when additional memory is requested and the remaining memory becomes 0.49 GB, the condition for the hook will be satisfied, but because the status of the hook is already Executed we will not execute it again. It looks like we will need to add checking for the hook condition after we update wasm_memory_threshold.

Luc found out about this mistake, and I am going to implement the fix for it. After the wasm_memory_threshold is updated, I will check and update the hook condition, but I am between two alternatives for this:

  1. Use the old hook status and the result of the checking for the hook condition with the new wasm_memory_threshold , i.e. update the hook status the same way as we do in any other case
  2. Use only the result of the checking for the hook condition with the new wasm_memory_threshold, so the hook status after the update of wasm_memory_threshold can be either ConditionNotSatisfied or Ready. I believe this alternative will require spec changes.

The difference can be observed in the case if, before the update of the wasm_memory_threshold hook status was Executed, the old wasm_memory_threshold was 3GB, remaining_wasm_memory was 1GB, and the new wasm_memory_threshold is 2GB. The former approach will keep the hook status to Executed, while the latter will set the hook status to Ready.

I am leaning towards the latter option but I do not have a strong opinion. Do you have an opinion on this?

github-merge-queue bot pushed a commit to dfinity/ic that referenced this pull request Jan 16, 2025
…rozen if it was scheduled before freezing (#3455)

Problem:
@mraszyk noticed
[here](dfinity/portal#3761 (comment))
that property:
”If the canister gets frozen immediately after the function is scheduled
for execution, the function will run once the canister's unfrozen if the
canister's wasm memory remains above the threshold t.”
is missing.

Solution:
When execution of the hook is stopped because of the freezing canister,
add the hook again to the task queue.
mergify bot pushed a commit to dfinity/motoko that referenced this pull request Jan 20, 2025
## Supporting Low Memory Hook in Motoko

The IC allows to implement a low memory hook, which is a warning trigger when main memory is becoming scarce.

For this purpose, a Motoko actor or actor class instance can implement the system function `lowmemory()`. This system function is scheduled when canister's free main memory space has fallen below the defined threshold `wasm_memory_threshold`, that is is part of the canister settings. In Motoko, `lowmemory()` implements the `canister_on_low_wasm_memory` hook defined in the IC specification.

Reference: dfinity/portal#3761

Example of implementing the low memory hook:
```
actor {
    system func lowmemory() : async* () {
        Debug.print("Low memory!");
    }
}
```

The following properties should be considered when using the low memory hook:
* The execution of `lowmemory` happens with a certain delay, as it is scheduled as a separate asynchronous message that runs after the message in which the threshold was crossed.
* Once executed, `lowmemory` will only be triggered again when the main memory free space grows above the threshold (e.g. by lowering the threshold or shrinking the main memory through canister reinstallation) and then again falls below the threshold.
* Traps or unhandled errors in `lowmemory` are ignored. They only revert the changes done in `lowmemory`.
* Due to its `async*` return type, the `lowmemory` function may send further messages and `await` results.
@mraszyk mraszyk changed the title [FINAL] feat: on low wasm memory hook feat: on low wasm memory hook Jan 23, 2025
@mraszyk
Copy link
Contributor Author

mraszyk commented Jan 23, 2025

In slack conversation:

Luc Bläser
Scenario:
Canister sets memory threshold at 1 GB.
Canister free memory is 0.99 GB -> hook is triggered
Now, canister sets memory threshold to 0.5 GB, free memory is still 0.99 GB
t1 > t remaining Wasm memory is greater than the Wasm memory threshold
And now canister allocates more memory, until 0.49 GB free memory
t2 > t1 remaining wasm memory falls below Wasm memory threshold
Shouldn't the hook now be retriggered?
Dragoljub Duric
That is a very good example. And I think that case may be missing. The reason is to check for the hook condition to be added after every memory_grow operation or after install/reinstall/uninstall/upgrade of the canister. But in this specific case, when memory is 0.99 GB hook will be executed and the status will be Executed since we do not have to check for the hook condition when we set a new threshold the status will remain Executed. Finally when additional memory is requested and the remaining memory becomes 0.49 GB, the condition for the hook will be satisfied, but because the status of the hook is already Executed we will not execute it again. It looks like we will need to add checking for the hook condition after we update wasm_memory_threshold.

Luc found out about this mistake, and I am going to implement the fix for it. After the wasm_memory_threshold is updated, I will check and update the hook condition, but I am between two alternatives for this:

  1. Use the old hook status and the result of the checking for the hook condition with the new wasm_memory_threshold , i.e. update the hook status the same way as we do in any other case
  2. Use only the result of the checking for the hook condition with the new wasm_memory_threshold, so the hook status after the update of wasm_memory_threshold can be either ConditionNotSatisfied or Ready. I believe this alternative will require spec changes.

The difference can be observed in the case if, before the update of the wasm_memory_threshold hook status was Executed, the old wasm_memory_threshold was 3GB, remaining_wasm_memory was 1GB, and the new wasm_memory_threshold is 2GB. The former approach will keep the hook status to Executed, while the latter will set the hook status to Ready.

I am leaning towards the latter option but I do not have a strong opinion. Do you have an opinion on this?

Personally, I prefer the former approach because it is consistent in terms of updating the hook status. If we decide to potentially rerun an already executed hook after updating canister settings, then it is not clear why not rerun an already executed hook also in other special cases.

@dragoljub-duric
Copy link

dragoljub-duric commented Jan 27, 2025

@mraszyk Thank you, that makes sense, I implemented the former approach. (link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interface-spec Changes to the IC Interface Specification
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants