Skip to content

Commit 6679a93

Browse files
authored
Motoko low_wasm_memory example (#1139)
- Adds Motoko low_wasm_memory example that matches the Rust example - Fixes the 'Broke pipe' in the Rust example CI - Changes the instructions in READMEs to update the canister settings before deploying to avoid growing the memory above the limit when a user takes too long between commands
1 parent cca3c53 commit 6679a93

File tree

8 files changed

+345
-74
lines changed

8 files changed

+345
-74
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
/motoko/internet_identity_integration/ @dfinity/identity
3434
/motoko/life/ @dfinity/languages
3535
/motoko/llm_chatbot/ @dfinity/ninja-devs
36+
/motoko/low_wasm_memory/ @dfinity/languages
3637
/motoko/minimal-counter-dapp/ @dfinity/growth
3738
/motoko/parallel_calls/ @dfinity/languages
3839
/motoko/pub-sub/ @dfinity/growth
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: motoko-low_wasm_memory
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
paths:
8+
- motoko/low_wasm_memory/**
9+
- .github/workflows/provision-darwin.sh
10+
- .github/workflows/provision-linux.sh
11+
- .github/workflows/motoko-low_wasm_memory-example.yml
12+
- .github/workflows/motoko-low_wasm_memory-skip.yml
13+
- .ic-commit
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
jobs:
18+
motoko-low_wasm_memory:
19+
runs-on: ${{ matrix.os }}
20+
strategy:
21+
matrix:
22+
include:
23+
- os: macos-15
24+
provision: .github/workflows/provision-darwin.sh
25+
- os: ubuntu-22.04
26+
provision: .github/workflows/provision-linux.sh
27+
steps:
28+
- uses: actions/checkout@v1
29+
- name: Provision
30+
run: bash ${{ matrix.provision }}
31+
- name: Motoko low_wasm_memory
32+
run: |
33+
set -euo pipefail
34+
pushd motoko/low_wasm_memory
35+
dfx start --background
36+
dfx deploy low_wasm_memory_hook
37+
dfx canister update-settings low_wasm_memory_hook --wasm-memory-limit 5000000 --wasm-memory-threshold 2000000
38+
dfx canister status low_wasm_memory_hook
39+
max_wait=50
40+
waited=0
41+
until [[ $(dfx canister call low_wasm_memory_hook --query getExecutedFunctionsOrder) == *onLowWasmMemory* ]]; do
42+
sleep 1
43+
dfx canister status low_wasm_memory_hook | grep 'Memory Size'
44+
waited=$((waited+1))
45+
if [ $waited -ge $max_wait ]; then
46+
echo "Timed out waiting for onLowWasmMemory event"
47+
exit 1
48+
fi
49+
done
50+
popd

.github/workflows/rust-low_wasm_memory-example.yml

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,36 @@ concurrency:
1515
group: ${{ github.workflow }}-${{ github.ref }}
1616
cancel-in-progress: true
1717
jobs:
18-
rust-low_wasm_memory-darwin:
19-
runs-on: macos-15
18+
rust-low_wasm_memory:
19+
runs-on: ${{ matrix.os }}
20+
strategy:
21+
matrix:
22+
include:
23+
- os: macos-15
24+
provision: .github/workflows/provision-darwin.sh
25+
- os: ubuntu-22.04
26+
provision: .github/workflows/provision-linux.sh
2027
steps:
2128
- uses: actions/checkout@v1
22-
- name: Provision Darwin
23-
run: bash .github/workflows/provision-darwin.sh
24-
- name: Rust low_wasm_memory Darwin
29+
- name: Provision
30+
run: bash ${{ matrix.provision }}
31+
- name: Rust low_wasm_memory
2532
run: |
33+
set -euo pipefail
2634
pushd rust/low_wasm_memory
2735
dfx start --background
2836
dfx deploy low_wasm_memory_hook
29-
dfx canister update-settings low_wasm_memory_hook --wasm-memory-limit 3000000 --wasm-memory-threshold 2000000
37+
dfx canister update-settings low_wasm_memory_hook --wasm-memory-limit 5000000 --wasm-memory-threshold 3000000
3038
dfx canister status low_wasm_memory_hook
31-
dfx canister call low_wasm_memory_hook --query get_executed_functions_order | grep -q 'OnLowWasmMemory' || while ! dfx canister call low_wasm_memory_hook --query get_executed_functions_order | grep -q 'OnLowWasmMemory'; do
32-
sleep 1
33-
done
34-
popd
35-
rust-low_wasm_memory-linux:
36-
runs-on: ubuntu-22.04
37-
steps:
38-
- uses: actions/checkout@v1
39-
- name: Provision Linux
40-
run: bash .github/workflows/provision-linux.sh
41-
- name: Rust low_wasm_memory Linux
42-
run: |
43-
pushd rust/low_wasm_memory
44-
dfx start --background
45-
dfx deploy low_wasm_memory_hook
46-
dfx canister update-settings low_wasm_memory_hook --wasm-memory-limit 3000000 --wasm-memory-threshold 2000000
47-
dfx canister status low_wasm_memory_hook
48-
dfx canister call low_wasm_memory_hook --query get_executed_functions_order | grep -q 'OnLowWasmMemory' || while ! dfx canister call low_wasm_memory_hook --query get_executed_functions_order | grep -q 'OnLowWasmMemory'; do
39+
max_wait=50
40+
waited=0
41+
until [[ $(dfx canister call low_wasm_memory_hook --query get_executed_functions_order) == *OnLowWasmMemory* ]]; do
4942
sleep 1
43+
dfx canister status low_wasm_memory_hook | grep 'Memory Size'
44+
waited=$((waited+1))
45+
if [ $waited -ge $max_wait ]; then
46+
echo "Timed out waiting for OnLowWasmMemory event"
47+
exit 1
48+
fi
5049
done
5150
popd

motoko/low_wasm_memory/README.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Low Wasm memory hook
2+
3+
The Internet Computer can automatically execute a special type of function called a low Wasm memory hook, which runs when the canister's available Wasm memory falls below the `wasm_memory_threshold`.
4+
5+
This Motoko example demonstrates using the low Wasm memory hook on ICP. If you're interested in how this example is implemented in Rust, check out the [Rust variation](../../rust/low_wasm_memory).
6+
7+
The example consists of a canister named `low_wasm_memory_hook` that increases usage of Wasm memory in every heartbeat execution until the low Wasm memory hook is run.
8+
9+
## Prerequisites
10+
This example requires an installation of:
11+
12+
- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install).
13+
- [x] Clone the example dapp project: `git clone https://github.com/dfinity/examples`
14+
15+
## Using the example
16+
17+
In this example, the canister will periodically increase its memory usage (as defined in the `heartbeat` function) until the low Wasm memory hook is run
18+
when the memory usage exceeds the `wasm_memory_threshold`.
19+
20+
- #### Step 1: Setup project environment
21+
22+
Navigate into the folder containing the project's files and start a local developer environment with the commands:
23+
24+
```sh
25+
cd examples/motoko/low_wasm_memory
26+
dfx start --clean
27+
```
28+
29+
This terminal will stay blocked, printing log messages, until the `Ctrl+C` is pressed or the `dfx stop` command is run.
30+
31+
- #### Step 2: Open another terminal window in the same directory:
32+
33+
```sh
34+
cd examples/motoko/low_wasm_memory
35+
```
36+
37+
- #### Step 3: Create the `low_wasm_memory_hook` canister
38+
39+
```sh
40+
dfx canister create low_wasm_memory_hook
41+
```
42+
43+
Example output:
44+
45+
```sh
46+
Created a wallet canister on the "local" network for user "default" with ID "uqqxf-5h777-77774-qaaaa-cai"
47+
low_wasm_memory_hook canister created with canister id: uxrrr-q7777-77774-qaaaq-cai
48+
```
49+
50+
- #### Step 4: Update canister settings
51+
52+
Update the canister's settings to modify the Wasm memory limit and threshold:
53+
54+
```sh
55+
dfx canister update-settings low_wasm_memory_hook --wasm-memory-limit 5000000 --wasm-memory-threshold 2000000
56+
```
57+
58+
- #### Step 5: Compile and deploy the `low_wasm_memory_hook` canister:
59+
60+
```sh
61+
dfx deploy low_wasm_memory_hook
62+
```
63+
64+
Example output:
65+
66+
```sh
67+
Deploying: low_wasm_memory_hook
68+
All canisters have already been created.
69+
Building canister 'low_wasm_memory_hook'.
70+
Installed code for canister low_wasm_memory_hook, with canister ID uxrrr-q7777-77774-qaaaq-cai
71+
Deployed canisters.
72+
URLs:
73+
Backend canister via Candid interface:
74+
low_wasm_memory_hook: http://127.0.0.1:4943/?canisterId=u6s2n-gx777-77774-qaaba-cai&id=uxrrr-q7777-77774-qaaaq-cai
75+
```
76+
77+
After the deployment, the memory usage periodically increases as defined in the `heartbeat` function.
78+
79+
The `dfx canister update-settings` command sets the 'wasm_memory_limit' to 5MiB and 'wasm_memory_threshold' to 2MiB.
80+
Hence, whenever the Wasm memory used by the canister is above 3MiB (in other words, the remaining Wasm memory is less than 'wasm_memory_threshold'), the low Wasm memory hook will be triggered.
81+
82+
This Motoko canister starts with ~2.3MiB of memory usage after the deployment, so it will trigger the low Wasm memory hook after allocating ~0.7MiB of memory.
83+
84+
You can verify that the canister settings got updated and check the current 'Memory Size' with the following command:
85+
86+
```sh
87+
dfx canister status low_wasm_memory_hook
88+
```
89+
90+
Example output:
91+
92+
```sh
93+
Canister status call result for low_wasm_memory_hook.
94+
Status: Running
95+
Controllers: 3apfx-fn75o-xtwmf-svjzg-hu5xt-bg2dr-ubczq-uhvlq-2a5gf-ya4fn-dqe uqqxf-5h777-77774-qaaaa-cai
96+
Memory allocation: 0 Bytes
97+
Compute allocation: 0 %
98+
Freezing threshold: 2_592_000 Seconds
99+
Idle cycles burned per day: 24_269_759 Cycles
100+
Memory Size: 2_374_914 Bytes
101+
Balance: 2_996_231_412_893 Cycles
102+
Reserved: 0 Cycles
103+
Reserved cycles limit: 5_000_000_000_000 Cycles
104+
Wasm memory limit: 5_000_000 Bytes
105+
Wasm memory threshold: 2_000_000 Bytes
106+
Module hash: 0x0c100162e1be161b9ef6fd95efb07f0541903c77a47e85a49a800705d6a09a11
107+
Number of queries: 0
108+
Instructions spent in queries: 0
109+
Total query request payload size: 0 Bytes
110+
Total query response payload size: 0 Bytes
111+
Log visibility: controllers
112+
```
113+
114+
- #### Step 6: After a few seconds, observe the output of the `getExecutedFunctionsOrder` query:
115+
116+
Query the canister by calling `getExecutedFunctionsOrder` to get the order of executed functions.
117+
118+
```sh
119+
dfx canister call low_wasm_memory_hook --query getExecutedFunctionsOrder
120+
```
121+
122+
Repeat the call until the last executed method is `onLowWasmMemory`.
123+
124+
Example output:
125+
126+
```sh
127+
(
128+
vec { variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { heartbeat }; variant { onLowWasmMemory };},
129+
)
130+
```
131+
132+
To repeat the example, you can redeploy the canister and reset its state with the following command:
133+
134+
```sh
135+
dfx deploy low_wasm_memory_hook --mode reinstall
136+
```
137+
138+
## Further learning
139+
1. [Low memory hook](https://internetcomputer.org/docs/motoko/main/canister-maintenance/memory) in Motoko documentation
140+
2. Have a look at the locally running dashboard. The URL is at the end of the `dfx start` command: `Dashboard: http://localhost:...`
141+
3. Check out `low_wasm_memory_hook` canister's Candid user interface. The URLs are at the end of the `dfx deploy` command: `low_wasm_memory_hook: http://127.0.0.1:...`
142+
143+
### Canister interface
144+
145+
The `low_wasm_memory_hook` canister provides the following interface:
146+
* `getExecutedFunctionsOrder` ; returns the vector with values of `FnType` (a variant with `#heartbeat` or `#onLowWasmMemory`) representing the order of functions executed.
147+
148+
Example usage:
149+
```sh
150+
dfx canister call low_wasm_memory_hook --query getExecutedFunctionsOrder
151+
```
152+
153+
## Resources
154+
For more information take a look at [low Wasm memory hook specification](https://internetcomputer.org/docs/references/ic-interface-spec#on-low-wasm-memory).
155+
156+
## Security considerations and best practices
157+
If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/current/references/security/) for developing on the Internet Computer. This example may not implement all the best practices.

motoko/low_wasm_memory/dfx.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"canisters": {
3+
"low_wasm_memory_hook": {
4+
"main": "src/low_wasm_memory_hook/main.mo",
5+
"type": "motoko"
6+
}
7+
},
8+
"defaults": {
9+
"build": {
10+
"args": "",
11+
"packtool": ""
12+
}
13+
},
14+
"output_env_file": ".env",
15+
"version": 1
16+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Array "mo:base/Array";
2+
import Deque "mo:base/Deque";
3+
import Buffer "mo:base/Buffer";
4+
5+
actor {
6+
// Types
7+
type FnType = {
8+
#heartbeat;
9+
#onLowWasmMemory;
10+
};
11+
12+
// State
13+
var fnOrderBuffer = Buffer.Buffer<FnType>(30);
14+
var bytes : Deque.Deque<[Nat]> = Deque.empty();
15+
var hookExecuted : Bool = false;
16+
17+
// Query function to get execution order
18+
public query func getExecutedFunctionsOrder() : async [FnType] {
19+
Buffer.toArray(fnOrderBuffer);
20+
};
21+
22+
// Heartbeat function that increases memory usage
23+
system func heartbeat() : async () {
24+
if (not hookExecuted) {
25+
fnOrderBuffer.add(#heartbeat);
26+
// Allocate more memory by creating a large array
27+
let chunk = Array.tabulate<Nat>(10_000, func _ = 0);
28+
bytes := Deque.pushBack(bytes, chunk);
29+
};
30+
};
31+
32+
// Low WASM memory hook
33+
system func lowmemory() : async* () {
34+
hookExecuted := true;
35+
fnOrderBuffer.add(#onLowWasmMemory);
36+
};
37+
};

0 commit comments

Comments
 (0)