Skip to content

Commit ff50550

Browse files
committed
context/data: memleak due to API (mis)use
Memory management is a foreign concept to python in general, howevrer it appears DNode and Context require .free() and .destroy() to be called, respectively, to avoid a memory leak. This seems easily avoidable by simply attaching a destructor to the class so the python garbage collector can clean up automatically when all references go out of scope. This change should also be compatible with existing integrations which may be aware of this as it sets the internal `cdata` reference to None and checks it to ensure it doesn't run the cleanup again. Signed-off-by: Brad House <[email protected]>
1 parent 8534053 commit ff50550

File tree

2 files changed

+15
-7
lines changed

2 files changed

+15
-7
lines changed

libyang/context.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ def __init__(
259259
raise self.error("cannot create context")
260260
self.external_module_loader = ContextExternalModuleLoader(self.cdata)
261261

262+
263+
def __del__(self):
264+
self.destroy()
265+
262266
def compile_schema(self):
263267
ret = lib.ly_ctx_compile(self.cdata)
264268
if ret != lib.LY_SUCCESS:

libyang/data.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ def __init__(self, context: "libyang.Context", cdata):
291291
self.attributes = None
292292
self.free_func = None # type: Callable[DNode]
293293

294+
def __del__(self):
295+
self.free()
296+
294297
def meta(self):
295298
ret = {}
296299
item = self.cdata.meta
@@ -996,13 +999,14 @@ def free_internal(self, with_siblings: bool = True) -> None:
996999
lib.lyd_free_tree(self.cdata)
9971000

9981001
def free(self, with_siblings: bool = True) -> None:
999-
try:
1000-
if self.free_func:
1001-
self.free_func(self) # pylint: disable=not-callable
1002-
else:
1003-
self.free_internal(with_siblings)
1004-
finally:
1005-
self.cdata = ffi.NULL
1002+
if self.cdata is not None:
1003+
try:
1004+
if self.free_func:
1005+
self.free_func(self) # pylint: disable=not-callable
1006+
else:
1007+
self.free_internal(with_siblings)
1008+
finally:
1009+
self.cdata = None
10061010

10071011
def leafref_link_node_tree(self) -> None:
10081012
"""

0 commit comments

Comments
 (0)