Skip to content

Commit 988fcd1

Browse files
committed
(re)add --stub-wasi option
Early versions of this project had such an option, but I had to drop as part of the refactoring required to support native extensions. The implementation is a bit tricky, since we need to import WASI during pre-initialization, but then stub out the imports in the final component. Normally, that would require significant surgery on the pre-initialized component, but I've "cheated" by applying the state snapshot to a stubbed component instead of the original one. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent f7912ae commit 988fcd1

17 files changed

Lines changed: 603 additions & 204 deletions

File tree

Cargo.lock

Lines changed: 160 additions & 87 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "componentize-py"
3-
version = "0.12.0"
3+
version = "0.13.0"
44
edition = "2021"
55
exclude = ["cpython"]
66

@@ -15,19 +15,26 @@ tar = "0.4.40"
1515
tempfile = "3.8.1"
1616
zstd = "0.13.0"
1717
componentize-py-shared = { path = "shared" }
18-
wasmparser = "0.118.0"
19-
wasm-encoder = "0.38.1"
20-
wit-parser = "0.13.1"
21-
wit-component = "0.20.0"
18+
# TODO: switch to upstream release versions of these deps once
19+
# https://github.com/bytecodealliance/wasm-tools/pull/1459 is merged and
20+
# released:
21+
wasm-encoder = { git = "https://github.com/dicej/wasm-tools", branch = "adapt-world-imports" }
22+
wit-parser = { git = "https://github.com/dicej/wasm-tools", branch = "adapt-world-imports" }
23+
wit-component = { git = "https://github.com/dicej/wasm-tools", branch = "adapt-world-imports" }
24+
wasmparser = { git = "https://github.com/dicej/wasm-tools", branch = "adapt-world-imports" }
2225
indexmap = "2.1.0"
2326
bincode = "1.3.3"
2427
heck = "0.4.1"
2528
pyo3 = { version = "0.20.0", features = ["abi3-py37", "extension-module"], optional = true }
26-
wasmtime = { path = "../wasmtime/crates/wasmtime", features = ["component-model"] }
27-
wasmtime-wasi = { path = "../wasmtime/crates/wasi" }
28-
wasi-common = { path = "../wasmtime/crates/wasi-common" }
29+
# TODO: switch to upstream release versions of these deps once
30+
# https://github.com/bytecodealliance/wasm-tools/pull/1459 is merged and
31+
# released, and Wasmtime has adopted those releases:
32+
wasmtime = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa", features = ["component-model"] }
33+
wasmtime-wasi = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa" }
34+
wasi-common = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa" }
2935
once_cell = "1.18.0"
3036
component-init = { git = "https://github.com/dicej/component-init" }
37+
wasm-convert = { git = "https://github.com/dicej/wasm-convert" }
3138
async-trait = "0.1.74"
3239
futures = "0.3.29"
3340
tokio = { version = "1.34.0", features = ["macros", "rt", "rt-multi-thread", "fs"] }
@@ -37,7 +44,7 @@ cap-std = "2.0.0"
3744
im-rc = "15.1.0"
3845
serde = { version = "1.0.193", features = ["derive"] }
3946
toml = "0.8.8"
40-
isyswasfa-transform = { path = "../transform" }
47+
isyswasfa-transform = { git = "https://github.com/dicej/isyswasfa-transform" }
4148
semver = "1.0.22"
4249

4350
[dev-dependencies]

examples/cli/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-cli] `command` world.
1010
## Prerequisites
1111

1212
* `Wasmtime` 18.0.0 or later
13-
* `componentize-py` 0.12.0
13+
* `componentize-py` 0.13.0
1414

1515
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1616
you don't have `cargo`, you can download and install from
1717
https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
1818

1919
```
2020
cargo install --version 18.0.0 wasmtime-cli
21-
pip install componentize-py==0.12.0
21+
pip install componentize-py==0.13.0
2222
```
2323

2424
## Running the demo

examples/http/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-http] `proxy` world.
1010
## Prerequisites
1111

1212
* `Wasmtime` 18.0.0 or later
13-
* `componentize-py` 0.12.0
13+
* `componentize-py` 0.13.0
1414

1515
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1616
you don't have `cargo`, you can download and install from
1717
https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
1818

1919
```
2020
cargo install --version 18.0.0 wasmtime-cli
21-
pip install componentize-py==0.12.0
21+
pip install componentize-py==0.13.0
2222
```
2323

2424
## Running the demo

examples/matrix-math/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ within a guest component.
1111
## Prerequisites
1212

1313
* `wasmtime` 18.0.0 or later
14-
* `componentize-py` 0.12.0
14+
* `componentize-py` 0.13.0
1515
* `NumPy`, built for WASI
1616

1717
Note that we use an unofficial build of NumPy since the upstream project does
@@ -23,7 +23,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
2323

2424
```
2525
cargo install --version 18.0.0 wasmtime-cli
26-
pip install componentize-py==0.12.0
26+
pip install componentize-py==0.13.0
2727
curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz
2828
tar xf numpy-wasi.tar.gz
2929
```

examples/sandbox/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Example: `sandbox`
2+
3+
This is an example of how to use
4+
[`wasmtime-py`](https://github.com/bytecodealliance/wasmtime-py) and
5+
[`componentize-py`](https://github.com/dicej/componentize-py) to execute
6+
sandboxed Python code snippets from within a Python app.
7+
8+
## Prerequisites
9+
10+
* `wasmtime-py` 18.0.0 or later
11+
* `componentize-py` 0.13.0
12+
13+
```
14+
pip install componentize-py==0.13.0 wasmtime==18.0.2
15+
```
16+
17+
## Running the demo
18+
19+
```
20+
componentize-py -d wit -w sandbox componentize guest -o sandbox.wasm
21+
python3 -m wasmtime.bindgen sandbox.wasm --out-dir sandbox
22+
python3 host.py "2 + 2"
23+
```
24+
25+
## Examples
26+
27+
`host.py` accepts zero or more `exec` strings (e.g. newline-delimited
28+
statements) followed by a final `eval` string (i.e. an expression). Note that
29+
any symbols you declare in an `exec` string must be explicitly added to the
30+
global scope using `global`. This ensures they are visible to subsequent `exec`
31+
and `eval` strings.
32+
33+
```shell-session
34+
$ python3 host.py "2 + 2"
35+
result: 4
36+
$ python3 host.py 'global foo
37+
def foo(): return 42' 'foo()'
38+
result: 42
39+
```
40+
41+
### Time limit
42+
43+
`host.py` enforces a twenty second timeout on guest execution. If and when the
44+
timeout is reached, `wasmtime` will raise a `Trap` error.
45+
46+
```shell-session
47+
$ python3 host.py 'while True: pass' '1'
48+
timeout!
49+
Traceback (most recent call last):
50+
File "/Users/dicej/p/component-sandbox-demo/host.py", line 31, in <module>
51+
result = sandbox.exec(store, arg)
52+
^^^^^^^^^^^^^^^^^^^^^^^^
53+
...
54+
```
55+
56+
### Memory limit
57+
58+
`host.py` limits guest memory usage to 20MB. Any attempt to allocate beyond
59+
that limit will fail.
60+
61+
```shell-session
62+
$ python3 host.py 'global foo
63+
foo = bytes(100 * 1024 * 1024)' 'foo[42]'
64+
exec error: MemoryError
65+
```

examples/sandbox/guest.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import sandbox
2+
from sandbox.types import Err
3+
import json
4+
5+
def handle(e: Exception):
6+
message = str(e)
7+
if message == '':
8+
raise Err(f"{type(e).__name__}")
9+
else:
10+
raise Err(f"{type(e).__name__}: {message}")
11+
12+
class Sandbox(sandbox.Sandbox):
13+
def eval(self, expression: str) -> str:
14+
try:
15+
return json.dumps(eval(expression))
16+
except Exception as e:
17+
handle(e)
18+
19+
def exec(self, statements: str):
20+
try:
21+
exec(statements)
22+
except Exception as e:
23+
handle(e)

examples/sandbox/host.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from sandbox import Root
2+
from sandbox.types import Ok, Err
3+
from wasmtime import Config, Engine, Store
4+
import json
5+
import sys
6+
from threading import Timer
7+
from typing import List, Tuple
8+
9+
TIMEOUT_SECONDS = 20
10+
MEMORY_LIMIT_BYTES = 20 * 1024 * 1024
11+
12+
args = sys.argv[1:]
13+
if len(args) == 0:
14+
print("usage: python3 host.py [<statement>...] <expression>", file=sys.stderr)
15+
exit(-1)
16+
17+
config = Config()
18+
config.epoch_interruption = True
19+
20+
def on_timeout(engine):
21+
print("timeout!")
22+
engine.increment_epoch()
23+
24+
engine = Engine(config)
25+
timer = Timer(TIMEOUT_SECONDS, on_timeout, args=(engine,))
26+
timer.start()
27+
28+
try:
29+
store = Store(engine)
30+
store.set_epoch_deadline(1)
31+
store.set_limits(memory_size=MEMORY_LIMIT_BYTES)
32+
33+
sandbox = Root(store)
34+
for arg in args[:-1]:
35+
result = sandbox.exec(store, arg)
36+
if isinstance(result, Err):
37+
print(f"exec error: {result.value}")
38+
exit(-1)
39+
40+
result = sandbox.eval(store, args[-1])
41+
if isinstance(result, Ok):
42+
result = json.loads(result.value)
43+
print(f"result: {result}")
44+
else:
45+
print(f"eval error: {result.value}")
46+
47+
finally:
48+
timer.cancel()

examples/sandbox/sandbox.wit

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package local:sandbox;
2+
3+
world sandbox {
4+
export eval: func(expression: string) -> result<string, string>;
5+
export exec: func(statements: string) -> result<_, string>;
6+
}

examples/tcp/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ making an outbound TCP request using `wasi-sockets`.
1111
## Prerequisites
1212

1313
* `Wasmtime` 18.0.0 or later
14-
* `componentize-py` 0.12.0
14+
* `componentize-py` 0.13.0
1515

1616
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1717
you don't have `cargo`, you can download and install from
1818
https://github.com/bytecodealliance/wasmtime/releases/tag/v18.0.0.
1919

2020
```
2121
cargo install --version 18.0.0 wasmtime-cli
22-
pip install componentize-py==0.12.0
22+
pip install componentize-py==0.13.0
2323
```
2424

2525
## Running the demo

0 commit comments

Comments
 (0)