Skip to content

Commit 9a156f1

Browse files
committed
refactor: clean up Plugin init ownership logic and format tests
Restructure Plugin.__init__ to make compiled_plugin ownership more explicit with type annotation, and apply consistent formatting to tests.
1 parent 253f598 commit 9a156f1

File tree

2 files changed

+51
-32
lines changed

2 files changed

+51
-32
lines changed

extism/extism.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,13 @@ def __init__(
552552
functions: Optional[List[Function]] = HOST_FN_REGISTRY,
553553
):
554554
# Track if we created the CompiledPlugin (so we know to free it)
555-
self._owns_compiled_plugin = not isinstance(plugin, CompiledPlugin)
556-
if self._owns_compiled_plugin:
557-
plugin = CompiledPlugin(plugin, wasi, functions)
558-
559-
self.compiled_plugin = plugin
555+
if isinstance(plugin, CompiledPlugin):
556+
self._owns_compiled_plugin = False
557+
self.compiled_plugin: Optional[CompiledPlugin] = plugin
558+
else:
559+
self._owns_compiled_plugin = True
560+
self.compiled_plugin = CompiledPlugin(plugin, wasi, functions)
561+
assert self.compiled_plugin is not None
560562
errmsg = _ffi.new("char**")
561563

562564
self.plugin = _lib.extism_plugin_new_from_compiled(
@@ -632,7 +634,10 @@ def __del__(self):
632634
_lib.extism_plugin_free(self.plugin)
633635
self.plugin = -1
634636
# Free the compiled plugin if we created it
635-
if getattr(self, "_owns_compiled_plugin", False) and self.compiled_plugin is not None:
637+
if (
638+
getattr(self, "_owns_compiled_plugin", False)
639+
and self.compiled_plugin is not None
640+
):
636641
self.compiled_plugin.__del__()
637642
self.compiled_plugin = None
638643

tests/test_extism.py

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ def test_can_free_plugin(self):
5252

5353
def test_plugin_del_frees_native_resources(self):
5454
"""Test that Plugin.__del__ properly frees native resources.
55-
55+
5656
This tests the fix for a bug where Plugin.__del__ checked for
5757
'self.pointer' instead of 'self.plugin', causing extism_plugin_free
5858
to never be called and leading to memory leaks.
59-
59+
6060
This also tests that __del__ can be safely called multiple times
6161
(via context manager exit and garbage collection) without causing
6262
double-free errors.
@@ -66,66 +66,80 @@ def test_plugin_del_frees_native_resources(self):
6666
self.assertEqual(j["count"], 1)
6767
# Plugin should own the compiled plugin it created
6868
self.assertTrue(plugin._owns_compiled_plugin)
69-
69+
7070
# Verify plugin was freed after exiting context
71-
self.assertEqual(plugin.plugin, -1,
72-
"Expected plugin.plugin to be -1 after __del__, indicating extism_plugin_free was called")
71+
self.assertEqual(
72+
plugin.plugin,
73+
-1,
74+
"Expected plugin.plugin to be -1 after __del__, indicating extism_plugin_free was called",
75+
)
7376
# Verify compiled plugin was also freed (since Plugin owned it)
74-
self.assertIsNone(plugin.compiled_plugin,
75-
"Expected compiled_plugin to be None after __del__, indicating it was also freed")
77+
self.assertIsNone(
78+
plugin.compiled_plugin,
79+
"Expected compiled_plugin to be None after __del__, indicating it was also freed",
80+
)
7681

7782
def test_compiled_plugin_del_frees_native_resources(self):
7883
"""Test that CompiledPlugin.__del__ properly frees native resources.
79-
84+
8085
Unlike Plugin, CompiledPlugin has no context manager so __del__ is only
8186
called once by garbage collection. This also tests that __del__ can be
8287
safely called multiple times without causing double-free errors.
8388
"""
8489
compiled = CompiledPlugin(self._manifest(), functions=[])
8590
# Verify pointer exists before deletion
86-
self.assertTrue(hasattr(compiled, 'pointer'))
91+
self.assertTrue(hasattr(compiled, "pointer"))
8792
self.assertNotEqual(compiled.pointer, -1)
88-
93+
8994
# Create a plugin from compiled to ensure it works
9095
plugin = extism.Plugin(compiled)
9196
j = json.loads(plugin.call("count_vowels", "test"))
9297
self.assertEqual(j["count"], 1)
93-
98+
9499
# Plugin should NOT own the compiled plugin (it was passed in)
95100
self.assertFalse(plugin._owns_compiled_plugin)
96-
101+
97102
# Clean up plugin first
98103
plugin.__del__()
99104
self.assertEqual(plugin.plugin, -1)
100-
105+
101106
# Compiled plugin should NOT have been freed by Plugin.__del__
102-
self.assertNotEqual(compiled.pointer, -1,
103-
"Expected compiled.pointer to NOT be -1 since Plugin didn't own it")
104-
107+
self.assertNotEqual(
108+
compiled.pointer,
109+
-1,
110+
"Expected compiled.pointer to NOT be -1 since Plugin didn't own it",
111+
)
112+
105113
# Now clean up compiled plugin manually
106114
compiled.__del__()
107-
115+
108116
# Verify compiled plugin was freed
109-
self.assertEqual(compiled.pointer, -1,
110-
"Expected compiled.pointer to be -1 after __del__, indicating extism_compiled_plugin_free was called")
117+
self.assertEqual(
118+
compiled.pointer,
119+
-1,
120+
"Expected compiled.pointer to be -1 after __del__, indicating extism_compiled_plugin_free was called",
121+
)
111122

112123
def test_extism_function_metadata_del_frees_native_resources(self):
113124
"""Test that _ExtismFunctionMetadata.__del__ properly frees native resources."""
125+
114126
def test_host_fn(inp: str) -> str:
115127
return inp
116-
128+
117129
func = TypeInferredFunction(None, "test_func", test_host_fn, [])
118130
metadata = _ExtismFunctionMetadata(func)
119-
131+
120132
# Verify pointer exists before deletion
121-
self.assertTrue(hasattr(metadata, 'pointer'))
133+
self.assertTrue(hasattr(metadata, "pointer"))
122134
self.assertIsNotNone(metadata.pointer)
123-
135+
124136
metadata.__del__()
125-
137+
126138
# Verify function was freed (pointer set to None)
127-
self.assertIsNone(metadata.pointer,
128-
"Expected metadata.pointer to be None after __del__, indicating extism_function_free was called")
139+
self.assertIsNone(
140+
metadata.pointer,
141+
"Expected metadata.pointer to be None after __del__, indicating extism_function_free was called",
142+
)
129143

130144
def test_errors_on_bad_manifest(self):
131145
self.assertRaises(

0 commit comments

Comments
 (0)