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: support IC low Wasm memory hook #4849

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

Conversation

luc-blaeser
Copy link
Contributor

@luc-blaeser luc-blaeser commented Jan 15, 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 onLowMemory(). 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, onLowMemory() 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 onLowMemory() : async () {
        Debug.print("Low memory!");
    }
}

The following properties should be considered when using the low memory hook:

  • The execution of onLowMemory 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, onLowMemory 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 onLowMemory are ignored. They only revert the changes done in onLowMemory.
  • Due to its async return type, the onLowMemory function may send further messages and await results.

@luc-blaeser luc-blaeser requested a review from a team as a code owner January 15, 2025 08:57
@luc-blaeser luc-blaeser self-assigned this Jan 15, 2025
Copy link

Comparing from cef9238 to 0ca1539:
In terms of gas, 5 tests regressed and the mean change is +0.6%.
In terms of size, no changes are observed in 5 tests.

@@ -394,12 +394,16 @@ let timer_type =
[Func (Local, Returns, [], [Prim Nat64], [])],
[Async (Fut, Var (default_scope_var, 0), unit)]))

let low_memory_type =
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a heads-up, these type definitions will live in type.ml after #4846. I'll take care of this in the migration.

@@ -1 +1 @@
M0129.mo:2.15-2.21: type error [M0129], unexpected system method named foobar, expected heartbeat or timer or preupgrade or postupgrade or inspect
M0129.mo:2.15-2.21: type error [M0129], unexpected system method named foobar, expected heartbeat or timer or preupgrade or postupgrade or onLowMemory or inspect
Copy link
Contributor

Choose a reason for hiding this comment

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

onLowMemory is kind-of clumsy, what about tightmemory? Would be more in line with the other names...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I see. In IC it is called canister_on_low_wasm_memory... Just thought to use a similar name for resemblance, but I am not fully happy either.
Maybe @crusso has some suggestion on how to name this.

Copy link
Contributor

@crusso crusso Jan 16, 2025

Choose a reason for hiding this comment

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

I think I prefer onLowMemory to tightmemory. I guess just memory or lowmemory if we want to keep it short and sweet like the others (https://internetcomputer.org/docs/current/motoko/main/reference/language-manual#system-fields), but onLowMemory is more meaningfull.

Copy link
Contributor

@ggreif ggreif Jan 16, 2025

Choose a reason for hiding this comment

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

What about lowmemory? After all we don't say onGlobalTimerExpiration or onHeartBeat either :-)

@luc-blaeser luc-blaeser requested a review from crusso January 15, 2025 17:45
@ggreif ggreif changed the title Support IC Low Wasm Memory Hook feat: support IC Low Wasm Memory Hook Jan 15, 2025
@ggreif ggreif changed the title feat: support IC Low Wasm Memory Hook feat: support IC low Wasm memory hook Jan 15, 2025

The following properties apply to the low memory hook:
* The execution of `onLowMemory` 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, `onLowMemory` is only triggered again when the main memory free space went above the threshold (e.g. by lowering the threshold or shrinking the main memory through canister reinstallation) and when the free space again fell below the threshold.
Copy link
Contributor

@crusso crusso Jan 16, 2025

Choose a reason for hiding this comment

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

Suggested change
* Once executed, `onLowMemory` is only triggered again when the main memory free space went above the threshold (e.g. by lowering the threshold or shrinking the main memory through canister reinstallation) and when the free space again fell below the threshold.
* Once executed, `onLowMemory` is only triggered again when the main memory free space exceeds the threshold (e.g. by lowering the threshold or shrinking the main memory through canister reinstallation) and when the free space again falls below the threshold.

Not sure I understand this... should that be an or? Or is the idea that it has to first exceed and then fall below the threshold.

The following properties apply to the low memory hook:
* The execution of `onLowMemory` 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, `onLowMemory` is only triggered again when the main memory free space went above the threshold (e.g. by lowering the threshold or shrinking the main memory through canister reinstallation) and when the free space again fell below the threshold.
* Traps or unhandled errors in `onLowMemory` are ignored. They only revert the changes done in `onLowMemory`.
Copy link
Contributor

@crusso crusso Jan 16, 2025

Choose a reason for hiding this comment

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

Suggested change
* Traps or unhandled errors in `onLowMemory` are ignored. They only revert the changes done in `onLowMemory`.
* Traps or unhandled errors in `onLowMemory` are ignored. Traps only revert the changes done in `onLowMemory`.

I assume errors don't revert.

await setMemoryThreshold(lowMemoryActor, threshold1);
await lowMemoryActor.allocateMemory();

// Not yet implemented on IC: Should retrigger when lowering threshold.
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh dear.

Copy link
Contributor

@crusso crusso left a comment

Choose a reason for hiding this comment

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

I guess we could consider allowing the return type to be async* but the method would need to force the async* somehow. That would avoid another scheduling hop.

Do we want merge this now or wait for the IC implementation to be fixed?

@crusso crusso self-requested a review January 16, 2025 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants