Skip to content

Commit 32ee4d4

Browse files
committed
add reference counting
1 parent 2e2309c commit 32ee4d4

File tree

2 files changed

+74
-45
lines changed

2 files changed

+74
-45
lines changed

libyang/context.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def get_yanglib_data(self, content_id_format=""):
273273
ret = lib.ly_ctx_get_yanglib_data(self.cdata, dnode, str2c(content_id_format))
274274
if ret != lib.LY_SUCCESS:
275275
raise self.error("cannot get yanglib data")
276-
return DNode.new(self, dnode[0])
276+
return DNode.new(self, dnode[0], self '''only reference to non-DNode''')
277277

278278
def destroy(self):
279279
if self.cdata is not None:
@@ -470,7 +470,7 @@ def create_data_path(
470470
if not dnode:
471471
raise self.error("cannot find created path")
472472

473-
return DNode.new(self, dnode)
473+
return DNode.new(self, dnode, parent)
474474

475475
def parse_op(
476476
self,
@@ -499,7 +499,7 @@ def parse_op(
499499
if ret != lib.LY_SUCCESS:
500500
raise self.error("failed to parse input data")
501501

502-
return DNode.new(self, op[0])
502+
return DNode.new(self, op[0], parent)
503503

504504
def parse_op_mem(
505505
self,
@@ -582,7 +582,7 @@ def parse_data(
582582
dnode = dnode[0]
583583
if dnode == ffi.NULL:
584584
return None
585-
return DNode.new(self, dnode)
585+
return DNode.new(self, dnode, parent)
586586

587587
def parse_data_mem(
588588
self,

libyang/data.py

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -279,22 +279,43 @@ class DNode:
279279

280280
__slots__ = ("context", "cdata", "attributes", "free_func", "__dict__")
281281

282-
def __init__(self, context: "libyang.Context", cdata):
282+
def __init__(self, context: "libyang.Context", cdata, refcnt_parent):
283283
"""
284284
:arg context:
285285
The libyang.Context python object.
286286
:arg cdata:
287287
The pointer to the C structure allocated by libyang.so.
288+
:arg refcnt_parent:
289+
New nodes may be created with internal references to the internal
290+
cdata tree. By holding a reference to the parent node that created
291+
us, we can force Python to keep proper reference counts so the
292+
destructor doesn't get called. The only time this may be set to
293+
None is if we can guarantee we are the initial creator of the
294+
cdata object.
288295
"""
289296
self.context = context
290297
self.cdata = cdata # C type: "struct lyd_node *"
291298
self.attributes = None
292299
self.free_func = None # type: Callable[DNode]
300+
self.refcnt_parent = refcnt_parent
293301

294302
def __del__(self):
295-
# Delete only the root node when it goes out of scope, this will delete
296-
# all children.
297-
if node.parent() is None:
303+
# Don't auto-destroy the node unless we know we're allowed. We can
304+
# determine this if refcnt_parent is set or not. If we have a parent
305+
# they own the registered cdata.
306+
# Functions that may create a DNode without a parent:
307+
# * Context.create_data_path() if parent is None
308+
# * Context.parse_op{_mem}() if parent is None
309+
# * Context.parse_data{_mem,_file}() if parent is None
310+
# * DNode.diff()
311+
# * DNode.duplicate() if parent is None
312+
# * dict_to_dnode() if parent is None
313+
# * DNode.merge_data_dict()
314+
# * DNode.iter_tree()
315+
# * DNode.leafref_nodes()
316+
# Functions that might unset the refcnt_parent:
317+
# * DNode.merge{_module}()
318+
if not self.refcnt_parent:
298319
self.free()
299320

300321
def meta(self):
@@ -458,17 +479,17 @@ def schema(self) -> SNode:
458479
def parent(self) -> Optional["DNode"]:
459480
if not self.cdata.parent:
460481
return None
461-
return self.new(self.context, self.cdata.parent)
482+
return self.new(self.context, self.cdata.parent, self)
462483

463484
def next(self) -> Optional["DNode"]:
464485
if not self.cdata.next:
465486
return None
466-
return self.new(self.context, self.cdata.next)
487+
return self.new(self.context, self.cdata.next, self)
467488

468489
def prev(self) -> Optional["DNode"]:
469490
if not self.cdata.prev:
470491
return None
471-
return self.new(self.context, self.cdata.prev)
492+
return self.new(self.context, self.cdata.prev, self)
472493

473494
def root(self) -> "DNode":
474495
node = self
@@ -480,7 +501,7 @@ def first_sibling(self) -> "DNode":
480501
n = lib.lyd_first_sibling(self.cdata)
481502
if n == self.cdata:
482503
return self
483-
return self.new(self.context, n)
504+
return self.new(self.context, n, self)
484505

485506
def siblings(self, include_self: bool = True) -> Iterator["DNode"]:
486507
n = lib.lyd_first_sibling(self.cdata)
@@ -489,14 +510,14 @@ def siblings(self, include_self: bool = True) -> Iterator["DNode"]:
489510
if include_self:
490511
yield self
491512
else:
492-
yield self.new(self.context, n)
513+
yield self.new(self.context, n, self)
493514
n = n.next
494515

495516
def find_path(self, path: str, output: bool = False):
496517
node = ffi.new("struct lyd_node **")
497518
ret = lib.lyd_find_path(self.cdata, str2c(path), output, node)
498519
if ret == lib.LY_SUCCESS:
499-
return DNode.new(self.context, node[0])
520+
return DNode.new(self.context, node[0], self)
500521
return None
501522

502523
def find_one(self, xpath: str) -> Optional["DNode"]:
@@ -514,7 +535,7 @@ def find_all(self, xpath: str) -> Iterator["DNode"]:
514535
node_set = node_set[0]
515536
try:
516537
for i in range(node_set.count):
517-
n = DNode.new(self.context, node_set.dnodes[i])
538+
n = DNode.new(self.context, node_set.dnodes[i], self)
518539
yield n
519540
finally:
520541
lib.ly_set_free(node_set, ffi.NULL)
@@ -599,7 +620,7 @@ def diff(
599620
if node_p[0] == ffi.NULL:
600621
return None
601622

602-
return self.new(self.context, node_p[0])
623+
return self.new(self.context, node_p[0], None ''' New allocation ''')
603624

604625
def diff_apply(self, diff_node: "DNode") -> None:
605626
node_p = ffi.new("struct lyd_node **")
@@ -636,7 +657,7 @@ def duplicate(
636657
else:
637658
lib.lyd_dup_single(self.cdata, parent, flags, node)
638659

639-
return DNode.new(self.context, node[0])
660+
return DNode.new(self.context, node[0], parent)
640661

641662
def merge_module(
642663
self,
@@ -654,6 +675,10 @@ def merge_module(
654675
if ret != lib.LY_SUCCESS:
655676
raise self.context.error("merge failed")
656677

678+
if destruct:
679+
source.cdata = None
680+
source.refcnt_parent = None
681+
657682
def merge(
658683
self,
659684
source: "DNode",
@@ -675,10 +700,14 @@ def merge(
675700

676701
self.cdata = node_p[0]
677702

703+
if destruct:
704+
source.cdata = None
705+
source.refcnt_parent = None
706+
678707
def iter_tree(self) -> Iterator["DNode"]:
679708
n = next_n = self.cdata
680709
while n != ffi.NULL:
681-
yield self.new(self.context, n)
710+
yield self.new(self.context, n, self)
682711

683712
next_n = lib.lyd_child(n)
684713
if next_n == ffi.NULL:
@@ -1031,7 +1060,7 @@ def leafref_nodes(self) -> Iterator["DNode"]:
10311060
if lib.lyd_leafref_get_links(term_node, out) != lib.LY_SUCCESS:
10321061
return
10331062
for n in ly_array_iter(out[0].leafref_nodes):
1034-
yield DNode.new(self.context, n)
1063+
yield DNode.new(self.context, n, self)
10351064

10361065
def __repr__(self):
10371066
cls = self.__class__
@@ -1052,7 +1081,7 @@ def _decorator(nodeclass):
10521081
return _decorator
10531082

10541083
@classmethod
1055-
def new(cls, context: "libyang.Context", cdata) -> "DNode":
1084+
def new(cls, context: "libyang.Context", cdata, refcnt_parent) -> "DNode":
10561085
cdata = ffi.cast("struct lyd_node *", cdata)
10571086
if not cdata.schema:
10581087
schemas = list(context.find_path(cls._get_path(cdata)))
@@ -1063,7 +1092,7 @@ def new(cls, context: "libyang.Context", cdata) -> "DNode":
10631092
nodecls = cls.NODETYPE_CLASS.get(cdata.schema.nodetype, None)
10641093
if nodecls is None:
10651094
raise TypeError("node type %s not implemented" % cdata.schema.nodetype)
1066-
return nodecls(context, cdata)
1095+
return nodecls(context, cdata, allow_destroy)
10671096

10681097
@staticmethod
10691098
def _get_path(cdata) -> str:
@@ -1096,7 +1125,7 @@ def children(self, no_keys=False) -> Iterator[DNode]:
10961125

10971126
while child:
10981127
if child.schema != ffi.NULL:
1099-
yield DNode.new(self.context, child)
1128+
yield DNode.new(self.context, child, self)
11001129
child = child.next
11011130

11021131
def __iter__(self):
@@ -1243,7 +1272,7 @@ def dict_to_dnode(
12431272

12441273
created = []
12451274

1246-
def _create_leaf(_parent, module, name, value, in_rpc_output=False):
1275+
def _create_leaf(_parent_cdata, module, name, value, in_rpc_output=False):
12471276
if value is not None:
12481277
if isinstance(value, bool):
12491278
value = str(value).lower()
@@ -1253,7 +1282,7 @@ def _create_leaf(_parent, module, name, value, in_rpc_output=False):
12531282
n = ffi.new("struct lyd_node **")
12541283
flags = newval_flags(rpc_output=in_rpc_output, store_only=store_only)
12551284
ret = lib.lyd_new_term(
1256-
_parent,
1285+
_parent_cdata,
12571286
module.cdata,
12581287
str2c(name),
12591288
str2c(value),
@@ -1262,21 +1291,21 @@ def _create_leaf(_parent, module, name, value, in_rpc_output=False):
12621291
)
12631292

12641293
if ret != lib.LY_SUCCESS:
1265-
if _parent:
1266-
parent_path = repr(DNode.new(module.context, _parent).path())
1294+
if _parent_cdata:
1295+
parent_path = repr(DNode.new(module.context, _parent_cdata, parent).path())
12671296
else:
12681297
parent_path = "module %r" % module.name()
12691298
raise module.context.error(
12701299
"failed to create leaf %r as a child of %s", name, parent_path
12711300
)
12721301
created.append(n[0])
12731302

1274-
def _create_container(_parent, module, name, in_rpc_output=False):
1303+
def _create_container(_parent_cdata, module, name, in_rpc_output=False):
12751304
n = ffi.new("struct lyd_node **")
1276-
ret = lib.lyd_new_inner(_parent, module.cdata, str2c(name), in_rpc_output, n)
1305+
ret = lib.lyd_new_inner(_parent_cdata, module.cdata, str2c(name), in_rpc_output, n)
12771306
if ret != lib.LY_SUCCESS:
1278-
if _parent:
1279-
parent_path = repr(DNode.new(module.context, _parent).path())
1307+
if _parent_cdata:
1308+
parent_path = repr(DNode.new(module.context, _parent_cdata, parent).path())
12801309
else:
12811310
parent_path = "module %r" % module.name()
12821311
raise module.context.error(
@@ -1287,20 +1316,20 @@ def _create_container(_parent, module, name, in_rpc_output=False):
12871316
created.append(n[0])
12881317
return n[0]
12891318

1290-
def _create_list(_parent, module, name, key_values, in_rpc_output=False):
1319+
def _create_list(_parent_cdata, module, name, key_values, in_rpc_output=False):
12911320
n = ffi.new("struct lyd_node **")
12921321
flags = newval_flags(rpc_output=in_rpc_output, store_only=store_only)
12931322
ret = lib.lyd_new_list(
1294-
_parent,
1323+
_parent_cdata,
12951324
module.cdata,
12961325
str2c(name),
12971326
flags,
12981327
n,
12991328
*[str2c(str(i)) for i in key_values],
13001329
)
13011330
if ret != lib.LY_SUCCESS:
1302-
if _parent:
1303-
parent_path = repr(DNode.new(module.context, _parent).path())
1331+
if _parent_cdata:
1332+
parent_path = repr(DNode.new(module.context, _parent_cdata, parent).path())
13041333
else:
13051334
parent_path = "module %r" % module.name()
13061335
raise module.context.error(
@@ -1356,7 +1385,7 @@ def _dic_keys(_dic, _schema):
13561385
return keys
13571386
return _dic.keys()
13581387

1359-
def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
1388+
def _to_dnode(_dic, _schema, _parent_cdata=ffi.NULL, in_rpc_output=False):
13601389
for key in _dic_keys(_dic, _schema):
13611390
if ":" in key:
13621391
prefix, name = key.split(":")
@@ -1379,7 +1408,7 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
13791408
value = _dic[key]
13801409

13811410
if isinstance(s, SLeaf):
1382-
_create_leaf(_parent, module, name, value, in_rpc_output)
1411+
_create_leaf(_parent_cdata, module, name, value, in_rpc_output)
13831412

13841413
elif isinstance(s, SLeafList):
13851414
if not isinstance(value, (list, tuple)):
@@ -1388,14 +1417,14 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
13881417
% (s.schema_path(), value)
13891418
)
13901419
for v in value:
1391-
_create_leaf(_parent, module, name, v, in_rpc_output)
1420+
_create_leaf(_parent_cdata, module, name, v, in_rpc_output)
13921421

13931422
elif isinstance(s, SRpc):
1394-
n = _create_container(_parent, module, name, in_rpc_output)
1423+
n = _create_container(_parent_cdata, module, name, in_rpc_output)
13951424
_to_dnode(value, s, n, rpcreply)
13961425

13971426
elif isinstance(s, SContainer):
1398-
n = _create_container(_parent, module, name, in_rpc_output)
1427+
n = _create_container(_parent_cdata, module, name, in_rpc_output)
13991428
_to_dnode(value, s, n, in_rpc_output)
14001429

14011430
elif isinstance(s, SList):
@@ -1420,31 +1449,31 @@ def _to_dnode(_dic, _schema, _parent=ffi.NULL, in_rpc_output=False):
14201449
except KeyError as e:
14211450
raise ValueError("Missing key %s in the list" % (k)) from e
14221451

1423-
n = _create_list(_parent, module, name, key_values, in_rpc_output)
1452+
n = _create_list(_parent_cdata, module, name, key_values, in_rpc_output)
14241453
_to_dnode(val, s, n, in_rpc_output)
14251454

14261455
elif isinstance(s, SNotif):
1427-
n = _create_container(_parent, module, name, in_rpc_output)
1456+
n = _create_container(_parent_cdata, module, name, in_rpc_output)
14281457
_to_dnode(value, s, n, in_rpc_output)
14291458

14301459
result = None
14311460

14321461
try:
14331462
if parent is not None:
1434-
_parent = parent.cdata
1463+
_parent_cdata = parent.cdata
14351464
_schema_parent = parent.schema()
14361465
else:
1437-
_parent = ffi.NULL
1466+
_parent_cdata = ffi.NULL
14381467
_schema_parent = module
14391468

14401469
_to_dnode(
14411470
dic,
14421471
_schema_parent,
1443-
_parent,
1472+
_parent_cdata,
14441473
in_rpc_output=rpcreply and isinstance(parent, DRpc),
14451474
)
14461475
if created:
1447-
result = DNode.new(module.context, created[0])
1476+
result = DNode.new(module.context, created[0], parent)
14481477
if validate:
14491478
result.root().validate(
14501479
no_state=no_state,

0 commit comments

Comments
 (0)