Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ permissions:
jobs:
test:
runs-on: ubuntu-latest
env:
RUST_BACKTRACE: "1"
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
Expand Down Expand Up @@ -65,4 +67,3 @@ jobs:
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ permissions:
jobs:
test:
runs-on: ubuntu-latest
env:
RUST_BACKTRACE: "1"
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
Expand All @@ -25,4 +27,3 @@ jobs:
- name: Run tests
run: |
python -m pytest

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ skipped, but package/build-script tests still run.
Current local result:

```text
52 passed
53 passed
```

To test a custom artifact manually:
Expand Down
44 changes: 26 additions & 18 deletions micropython_wasm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def close(self) -> None:
if self._started:
self._request_queue.put({"op": "close"})
if self._thread is not None:
self._thread.join(timeout=1.0)
self._thread.join()

def __enter__(self) -> MicroPythonSession:
if self._closed:
Expand Down Expand Up @@ -449,23 +449,30 @@ def capture_stderr(data: bytes) -> None:
_define_host_call(linker, store, host_functions, Func, FuncType, ValType)

try:
module = Module.from_file(engine, str(self.wasm_path))
instance = linker.instantiate(store, module)
start = instance.exports(store).get("_start")
if start is None:
raise MicroPythonWasmError("WASI module does not export _start")
if not isinstance(start, Func):
raise MicroPythonWasmError("WASI module _start export is not callable")
start(store)
except ExitTrap as exc:
if getattr(exc, "code", 0) not in (0, None):
raise MicroPythonWasmError(
f"guest exited with code {exc.code}"
) from exc
except Trap as exc:
raise MicroPythonWasmError(f"guest trapped: {exc}") from exc
except WasmtimeError as exc:
raise MicroPythonWasmError(f"wasmtime error: {exc}") from exc
try:
module = Module.from_file(engine, str(self.wasm_path))
instance = linker.instantiate(store, module)
start = instance.exports(store).get("_start")
if start is None:
raise MicroPythonWasmError("WASI module does not export _start")
if not isinstance(start, Func):
raise MicroPythonWasmError(
"WASI module _start export is not callable"
)
start(store)
except ExitTrap as exc:
if getattr(exc, "code", 0) not in (0, None):
raise MicroPythonWasmError(
f"guest exited with code {exc.code}"
) from exc
except Trap as exc:
raise MicroPythonWasmError(f"guest trapped: {exc}") from exc
except WasmtimeError as exc:
raise MicroPythonWasmError(f"wasmtime error: {exc}") from exc
finally:
with self._callback_lock:
self._store = None
self._thread_host_functions = None

def _session_next(self) -> dict[str, object]:
request = self._request_queue.get()
Expand Down Expand Up @@ -634,6 +641,7 @@ def capture_stderr(data: bytes) -> None:
finally:
if timer is not None:
timer.cancel()
timer.join()

return RunResult(
stdout="".join(stdout_parts),
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "micropython-wasm"
version = "0.1"
version = "0.1a0"
description = "MicroPython packaged in WASM for wasmtime"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
12 changes: 12 additions & 0 deletions tests/test_persistent_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,15 @@ def test_persistent_session_close_is_idempotent():

with pytest.raises(MicroPythonSessionClosed):
session.run("print(1)")


def test_persistent_session_close_releases_thread_resources():
session = MicroPythonSession()
session.run("print(1)")

session.close()

assert session._thread is not None
assert not session._thread.is_alive()
assert session._store is None
assert session._thread_host_functions is None