Skip to content

Commit

Permalink
Enable explicit destruction of all resources
Browse files Browse the repository at this point in the history
This commit adds a suite of methods to most of the types in this package
to enable explicit destruction of resources as necessary. Specifically
most classes which own a pointer internally now sport some new methods:

* `__enter__` and `__exit__` enable using the objects in `with` blocks.
* `close` enables explicitly deallocating the object.

All objects internally now validate that they have not been closed
before operating, raising a `ValueError` if a closed object is operated
on.
  • Loading branch information
alexcrichton committed Feb 15, 2024
1 parent 04ce48c commit 458af19
Show file tree
Hide file tree
Showing 20 changed files with 396 additions and 287 deletions.
12 changes: 12 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
from contextlib import closing

from wasmtime import *

Expand Down Expand Up @@ -34,3 +35,14 @@ def test_smoke(self):
config.consume_fuel = True
config.wasm_relaxed_simd = True
config.wasm_relaxed_simd_deterministic = True

with closing(config) as config:
pass

config.close()

with self.assertRaises(ValueError):
Engine(config)

with self.assertRaises(ValueError):
config.cache = True
2 changes: 1 addition & 1 deletion tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ def test_errors(self):
Engine(3) # type: ignore
config = Config()
Engine(config)
with self.assertRaises(WasmtimeError):
with self.assertRaises(ValueError):
Engine(config)
9 changes: 9 additions & 0 deletions tests/test_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ def test_smoke(self):
Store()
Store(Engine())

with Store() as store:
pass

store = Store()
store.close()
store.close()
with self.assertRaises(ValueError):
store.set_epoch_deadline(1)

def test_errors(self):
with self.assertRaises(TypeError):
Store(3) # type: ignore
Expand Down
1 change: 1 addition & 0 deletions wasmtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
libraries are intended to be quite similar.
"""

from ._managed import Managed
from ._error import WasmtimeError, ExitTrap
from ._config import Config
from ._engine import Engine
Expand Down
61 changes: 29 additions & 32 deletions wasmtime/_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from . import _ffi as ffi
from ctypes import *
import ctypes
from wasmtime import WasmtimeError
from wasmtime import WasmtimeError, Managed
import typing


Expand All @@ -13,18 +13,19 @@ def setter_property(fset: typing.Callable) -> property:
return prop


class Config:
class Config(Managed["ctypes._Pointer[ffi.wasm_config_t]"]):
"""
Global configuration, used to create an `Engine`.
A `Config` houses a number of configuration options which tweaks how wasm
code is compiled or generated.
"""

_ptr: "ctypes._Pointer[ffi.wasm_config_t]"

def __init__(self) -> None:
self._ptr = ffi.wasm_config_new()
self._set_ptr(ffi.wasm_config_new())

def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_config_t]") -> None:
ffi.wasm_config_delete(ptr)

@setter_property
def debug_info(self, enable: bool) -> None:
Expand All @@ -35,7 +36,7 @@ def debug_info(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_debug_info_set(self._ptr, enable)
ffi.wasmtime_config_debug_info_set(self.ptr(), enable)

@setter_property
def wasm_threads(self, enable: bool) -> None:
Expand All @@ -47,7 +48,7 @@ def wasm_threads(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_threads_set(self._ptr, enable)
ffi.wasmtime_config_wasm_threads_set(self.ptr(), enable)

@setter_property
def wasm_reference_types(self, enable: bool) -> None:
Expand All @@ -59,7 +60,7 @@ def wasm_reference_types(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_reference_types_set(self._ptr, enable)
ffi.wasmtime_config_wasm_reference_types_set(self.ptr(), enable)

@setter_property
def wasm_simd(self, enable: bool) -> None:
Expand All @@ -71,7 +72,7 @@ def wasm_simd(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_simd_set(self._ptr, enable)
ffi.wasmtime_config_wasm_simd_set(self.ptr(), enable)

@setter_property
def wasm_bulk_memory(self, enable: bool) -> None:
Expand All @@ -83,7 +84,7 @@ def wasm_bulk_memory(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_bulk_memory_set(self._ptr, enable)
ffi.wasmtime_config_wasm_bulk_memory_set(self.ptr(), enable)

@setter_property
def wasm_multi_value(self, enable: bool) -> None:
Expand All @@ -95,7 +96,7 @@ def wasm_multi_value(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_multi_value_set(self._ptr, enable)
ffi.wasmtime_config_wasm_multi_value_set(self.ptr(), enable)

@setter_property
def wasm_multi_memory(self, enable: bool) -> None:
Expand All @@ -107,7 +108,7 @@ def wasm_multi_memory(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_multi_memory_set(self._ptr, enable)
ffi.wasmtime_config_wasm_multi_memory_set(self.ptr(), enable)

@setter_property
def wasm_memory64(self, enable: bool) -> None:
Expand All @@ -119,7 +120,7 @@ def wasm_memory64(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_memory64_set(self._ptr, enable)
ffi.wasmtime_config_wasm_memory64_set(self.ptr(), enable)

@setter_property
def wasm_relaxed_simd(self, enable: bool) -> None:
Expand All @@ -131,7 +132,7 @@ def wasm_relaxed_simd(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_relaxed_simd_set(self._ptr, enable)
ffi.wasmtime_config_wasm_relaxed_simd_set(self.ptr(), enable)

@setter_property
def wasm_relaxed_simd_deterministic(self, enable: bool) -> None:
Expand All @@ -145,7 +146,7 @@ def wasm_relaxed_simd_deterministic(self, enable: bool) -> None:

if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_wasm_relaxed_simd_deterministic_set(self._ptr, enable)
ffi.wasmtime_config_wasm_relaxed_simd_deterministic_set(self.ptr(), enable)

@setter_property
def strategy(self, strategy: str) -> None:
Expand All @@ -159,35 +160,35 @@ def strategy(self, strategy: str) -> None:
"""

if strategy == "auto":
ffi.wasmtime_config_strategy_set(self._ptr, 0)
ffi.wasmtime_config_strategy_set(self.ptr(), 0)
elif strategy == "cranelift":
ffi.wasmtime_config_strategy_set(self._ptr, 1)
ffi.wasmtime_config_strategy_set(self.ptr(), 1)
else:
raise WasmtimeError("unknown strategy: " + str(strategy))

@setter_property
def cranelift_debug_verifier(self, enable: bool) -> None:
if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_cranelift_debug_verifier_set(self._ptr, enable)
ffi.wasmtime_config_cranelift_debug_verifier_set(self.ptr(), enable)

@setter_property
def cranelift_opt_level(self, opt_level: str) -> None:
if opt_level == "none":
ffi.wasmtime_config_cranelift_opt_level_set(self._ptr, 0)
ffi.wasmtime_config_cranelift_opt_level_set(self.ptr(), 0)
elif opt_level == "speed":
ffi.wasmtime_config_cranelift_opt_level_set(self._ptr, 1)
ffi.wasmtime_config_cranelift_opt_level_set(self.ptr(), 1)
elif opt_level == "speed_and_size":
ffi.wasmtime_config_cranelift_opt_level_set(self._ptr, 2)
ffi.wasmtime_config_cranelift_opt_level_set(self.ptr(), 2)
else:
raise WasmtimeError("unknown opt level: " + str(opt_level))

@setter_property
def profiler(self, profiler: str) -> None:
if profiler == "none":
ffi.wasmtime_config_profiler_set(self._ptr, 0)
ffi.wasmtime_config_profiler_set(self.ptr(), 0)
elif profiler == "jitdump":
ffi.wasmtime_config_profiler_set(self._ptr, 1)
ffi.wasmtime_config_profiler_set(self.ptr(), 1)
else:
raise WasmtimeError("unknown profiler: " + str(profiler))

Expand All @@ -207,9 +208,9 @@ def cache(self, enabled: typing.Union[bool, str]) -> None:
if isinstance(enabled, bool):
if not enabled:
raise WasmtimeError("caching cannot be explicitly disabled")
error = ffi.wasmtime_config_cache_config_load(self._ptr, None)
error = ffi.wasmtime_config_cache_config_load(self.ptr(), None)
elif isinstance(enabled, str):
error = ffi.wasmtime_config_cache_config_load(self._ptr,
error = ffi.wasmtime_config_cache_config_load(self.ptr(),
c_char_p(enabled.encode('utf-8')))
else:
raise TypeError("expected string or bool")
Expand All @@ -227,7 +228,7 @@ def epoch_interruption(self, enabled: bool) -> None:
val = 1
else:
val = 0
ffi.wasmtime_config_epoch_interruption_set(self._ptr, val)
ffi.wasmtime_config_epoch_interruption_set(self.ptr(), val)

@setter_property
def consume_fuel(self, instances: bool) -> None:
Expand All @@ -240,7 +241,7 @@ def consume_fuel(self, instances: bool) -> None:
"""
if not isinstance(instances, bool):
raise TypeError('expected an bool')
ffi.wasmtime_config_consume_fuel_set(self._ptr, instances)
ffi.wasmtime_config_consume_fuel_set(self.ptr(), instances)

@setter_property
def parallel_compilation(self, enable: bool) -> None:
Expand All @@ -252,8 +253,4 @@ def parallel_compilation(self, enable: bool) -> None:
"""
if not isinstance(enable, bool):
raise TypeError('expected a bool')
ffi.wasmtime_config_parallel_compilation_set(self._ptr, enable)

def __del__(self) -> None:
if hasattr(self, '_ptr'):
ffi.wasm_config_delete(self._ptr)
ffi.wasmtime_config_parallel_compilation_set(self.ptr(), enable)
23 changes: 9 additions & 14 deletions wasmtime/_engine.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
from . import _ffi as ffi
from wasmtime import Config, WasmtimeError
from wasmtime import Config, WasmtimeError, Managed
from typing import Optional
import ctypes


class Engine:
_ptr: "ctypes._Pointer[ffi.wasm_engine_t]"
class Engine(Managed["ctypes._Pointer[ffi.wasm_engine_t]"]):

def __init__(self, config: Optional[Config] = None):
if config is None:
self._ptr = ffi.wasm_engine_new()
self._set_ptr(ffi.wasm_engine_new())
elif not isinstance(config, Config):
raise TypeError("expected Config")
elif not hasattr(config, '_ptr'):
raise WasmtimeError("Config already used")
else:
ptr = config._ptr
delattr(config, '_ptr')
self._ptr = ffi.wasm_engine_new_with_config(ptr)
ptr = config._consume()
self._set_ptr(ffi.wasm_engine_new_with_config(ptr))

def increment_epoch(self) -> None:
ffi.wasmtime_engine_increment_epoch(self._ptr)
def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_engine_t]") -> None:
ffi.wasm_engine_delete(ptr)

def __del__(self) -> None:
if hasattr(self, '_ptr'):
ffi.wasm_engine_delete(self._ptr)
def increment_epoch(self) -> None:
ffi.wasmtime_engine_increment_epoch(self.ptr())
30 changes: 14 additions & 16 deletions wasmtime/_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
from . import _ffi as ffi
import ctypes
from typing import Optional
from wasmtime import Managed


class WasmtimeError(Exception):
_ptr: "Optional[ctypes._Pointer[ffi.wasmtime_error_t]]"
_message: Optional[str]
class WasmtimeError(Exception, Managed["ctypes._Pointer[ffi.wasmtime_error_t]"]):
__message: Optional[str]

def __init__(self, message: str):
self._message = message
self._ptr = None
self.__message = message

def _delete(self, ptr: "ctypes._Pointer[ffi.wasmtime_error_t]") -> None:
ffi.wasmtime_error_delete(ptr)

@classmethod
def _from_ptr(cls, ptr: "ctypes._Pointer") -> 'WasmtimeError':
Expand All @@ -21,29 +23,25 @@ def _from_ptr(cls, ptr: "ctypes._Pointer") -> 'WasmtimeError':
exit_code = c_int(0)
if ffi.wasmtime_error_exit_status(ptr, byref(exit_code)):
exit_trap: ExitTrap = ExitTrap.__new__(ExitTrap)
exit_trap._ptr = ptr
exit_trap._message = None
exit_trap._set_ptr(ptr)
exit_trap.__message = None
exit_trap.code = exit_code.value
return exit_trap

err: WasmtimeError = cls.__new__(cls)
err._ptr = ptr
err._message = None
err._set_ptr(ptr)
err.__message = None
return err

def __str__(self) -> str:
if self._message:
return self._message
if self.__message:
return self.__message
message_vec = ffi.wasm_byte_vec_t()
ffi.wasmtime_error_message(self._ptr, byref(message_vec))
ffi.wasmtime_error_message(self.ptr(), byref(message_vec))
message = ffi.to_str(message_vec)
ffi.wasm_byte_vec_delete(byref(message_vec))
return message

def __del__(self) -> None:
if hasattr(self, '_ptr') and self._ptr:
ffi.wasmtime_error_delete(self._ptr)


class ExitTrap(WasmtimeError):
"""
Expand Down
10 changes: 5 additions & 5 deletions wasmtime/_extern.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from . import _ffi as ffi
import ctypes
from ._exportable import AsExtern
from wasmtime import WasmtimeError
from wasmtime import WasmtimeError, Managed


def wrap_extern(ptr: ffi.wasmtime_extern_t) -> AsExtern:
Expand Down Expand Up @@ -41,9 +41,9 @@ def get_extern_ptr(item: AsExtern) -> ffi.wasmtime_extern_t:
raise TypeError("expected a Func, Global, Memory, Table, Module, or Instance")


class Extern:
class Extern(Managed["ctypes._Pointer[ffi.wasm_extern_t]"]):
def __init__(self, ptr: "ctypes._Pointer[ffi.wasm_extern_t]"):
self.ptr = ptr
self._set_ptr(ptr)

def __del__(self) -> None:
ffi.wasm_extern_delete(self.ptr)
def _delete(self, ptr: "ctypes._Pointer[ffi.wasm_extern_t]") -> None:
ffi.wasm_extern_delete(ptr)
Loading

0 comments on commit 458af19

Please sign in to comment.