From ea3bac6b9eb38aac94d086630b9815bfca7197fd Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 1 Jun 2026 20:34:44 -0700 Subject: [PATCH 1/2] Release 0.1a0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 821a04b..f13a7d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" From 0a1bc63eb50bc9b0bba71515d2933b43fc3b2412 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 1 Jun 2026 20:40:57 -0700 Subject: [PATCH 2/2] Trying out a fix for Python 3.11 in CI, refs #3 --- .github/workflows/publish.yml | 3 ++- .github/workflows/test.yml | 3 ++- README.md | 2 +- micropython_wasm/__init__.py | 44 +++++++++++++++++++------------- tests/test_persistent_session.py | 12 +++++++++ 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a93b5ee..cb8443b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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"] @@ -65,4 +67,3 @@ jobs: path: dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3eb8719..b1c7334 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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"] @@ -25,4 +27,3 @@ jobs: - name: Run tests run: | python -m pytest - diff --git a/README.md b/README.md index af75cfe..51c1125 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/micropython_wasm/__init__.py b/micropython_wasm/__init__.py index 7f2ca21..b0ef9a2 100644 --- a/micropython_wasm/__init__.py +++ b/micropython_wasm/__init__.py @@ -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: @@ -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() @@ -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), diff --git a/tests/test_persistent_session.py b/tests/test_persistent_session.py index 368af2a..bb05fdf 100644 --- a/tests/test_persistent_session.py +++ b/tests/test_persistent_session.py @@ -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