Skip to content

Commit 449a9a9

Browse files
Merge pull request #2015 from KLayout/bugfix/issue-2012
Bugfix/issue 2012
2 parents efe90cf + 78e2074 commit 449a9a9

File tree

4 files changed

+58
-21
lines changed

4 files changed

+58
-21
lines changed

src/pya/pya/pyaConvert.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, boo
440440
obj = clsact->create_from_adapted (obj);
441441
}
442442

443-
// we wil own the new object
443+
// we will own the new object
444444
pass_obj = true;
445445

446446
}
@@ -488,6 +488,7 @@ object_to_python (void *obj, PYAObjectBase *self, const gsi::ClassBase *cls, boo
488488
PYAObjectBase *new_object = PYAObjectBase::from_pyobject_unsafe (new_pyobject);
489489
new (new_object) PYAObjectBase (clsact, new_pyobject);
490490
new_object->set (obj, pass_obj, is_const, can_destroy);
491+
491492
return new_pyobject;
492493

493494
}

src/pya/pya/pyaObject.cc

+42-18
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,16 @@ PYAObjectBase::object_destroyed ()
238238

239239
detach ();
240240

241-
// NOTE: this may delete "this"!
242-
if (!prev_owner) {
243-
Py_DECREF (py_object ());
241+
if (! prev_owner) {
242+
const gsi::ClassBase *cls = cls_decl ();
243+
if (cls && cls->is_managed ()) {
244+
// If the object was owned on C++ side before, we need to decrement the
245+
// reference count to reflect the fact, that there no longer is an external
246+
// owner.
247+
// NOTE: this may delete "this", hence we return
248+
Py_DECREF (py_object ());
249+
return;
250+
}
244251
}
245252

246253
}
@@ -249,31 +256,44 @@ PYAObjectBase::object_destroyed ()
249256
void
250257
PYAObjectBase::release ()
251258
{
259+
// "release" means to release ownership of the C++ object on the C++ side.
260+
// In other words: to transfer ownership to the script side. Specifically to
261+
// transfer it to the Python domain.
262+
252263
// If the object is managed we first reset the ownership of all other clients
253264
// and then make us the owner
254265
const gsi::ClassBase *cls = cls_decl ();
255266
if (cls && cls->is_managed ()) {
256267
void *o = obj ();
257268
if (o) {
269+
// NOTE: "keep" means "move ownership of the C++ object to C++". In other words,
270+
// release ownership of the C++ object on script side.
258271
cls->gsi_object (o)->keep ();
272+
if (! m_owned) {
273+
// We have to *decrement* the reference count as now there is no other entity
274+
// holding a reference to this Python object.
275+
// NOTE: this may delete "this", hence we return
276+
m_owned = true;
277+
Py_DECREF (py_object ());
278+
return;
279+
}
259280
}
260281
}
261282

262-
// NOTE: this is fairly dangerous
263-
if (!m_owned) {
264-
m_owned = true;
265-
// NOTE: this may delete "this"! TODO: this should not happen. Can we assert that somehow?
266-
Py_DECREF (py_object ());
267-
}
283+
m_owned = true;
268284
}
269285

270286
void
271287
PYAObjectBase::keep_internal ()
272288
{
273289
if (m_owned) {
290+
// "keep" means to transfer ownership of the C++ object to C++ side, while
291+
// "m_owned" refers to ownership on the Python side. So if we perform this
292+
// transfer, we need to reflect the fact that there is another entity holding
293+
// a reference.
274294
Py_INCREF (py_object ());
275-
m_owned = false;
276295
}
296+
m_owned = false;
277297
}
278298

279299
void
@@ -284,9 +304,11 @@ PYAObjectBase::keep ()
284304
void *o = obj ();
285305
if (o) {
286306
if (cls->is_managed ()) {
307+
// dispatch the keep notification - this will call "keep_internal" through the
308+
// event handler (StatusChangedListener)
287309
cls->gsi_object (o)->keep ();
288310
} else {
289-
keep_internal ();
311+
m_owned = false;
290312
}
291313
}
292314
}
@@ -341,16 +363,18 @@ PYAObjectBase::set (void *obj, bool owned, bool const_ref, bool can_destroy)
341363

342364
if (cls->is_managed ()) {
343365
gsi::ObjectBase *gsi_object = cls->gsi_object (m_obj);
344-
// Consider the case of "keep inside constructor"
345366
if (gsi_object->already_kept ()) {
346-
keep_internal ();
367+
// Consider the case of "keep inside constructor"
368+
m_owned = false;
369+
}
370+
if (! m_owned) {
371+
// "m_owned = false" means ownership of the C++ object is on C++ side,
372+
// and not on script side. In that case, we need to increment the
373+
// reference count to reflect the fact that there is an external owner.
374+
Py_INCREF (py_object ());
347375
}
348376
gsi_object->status_changed_event ().add (mp_listener, &StatusChangedListener::object_status_changed);
349377
}
350-
351-
if (!m_owned) {
352-
Py_INCREF (py_object ());
353-
}
354378
}
355379

356380
// TODO: a static (singleton) instance is not thread-safe
@@ -587,7 +611,7 @@ PYAObjectBase::obj ()
587611
throw tl::Exception (tl::to_string (tr ("Object has been destroyed already")));
588612
} else {
589613
// delayed creation of a detached C++ object ..
590-
set(cls_decl ()->create (), true, false, true);
614+
set (cls_decl ()->create (), true, false, true);
591615
}
592616
}
593617

testdata/python/dbLayoutTest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ def test_6_Layout_props2(self):
684684
ly = pya.Layout(True)
685685
pv = [ [ 17, "a" ], [ "b", [ 1, 5, 7 ] ] ]
686686
pid = ly.properties_id( pv )
687-
# does not work? @@@
687+
# does not work?
688688
# pv = { 17: "a", "b": [ 1, 5, 7 ] }
689689
# pid2 = ly.properties_id( pv )
690690
# self.assertEqual( pid, pid2 )

testdata/python/dbShapesTest.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
class DBShapesTest(unittest.TestCase):
2525

2626
# Shape objects as hashes
27-
def test_12(self):
27+
def test_1(self):
2828

2929
s = pya.Shapes()
3030
s1 = s.insert(pya.Box(1, 2, 3, 4))
@@ -50,6 +50,18 @@ def test_12(self):
5050
self.assertEqual(h[s2], 2)
5151
self.assertEqual(h[s3], 3)
5252

53+
# Issue #2012 (reference count)
54+
def test_2(self):
55+
56+
ly = pya.Layout()
57+
top = ly.create_cell("TOP")
58+
l1 = ly.layer(1, 0)
59+
top.shapes(l1).insert(pya.Box(0, 0, 100, 200))
60+
61+
shapes = top.shapes(l1)
62+
self.assertEqual(sys.getrefcount(shapes), 2)
63+
64+
5365
# run unit tests
5466
if __name__ == '__main__':
5567
suite = unittest.TestLoader().loadTestsFromTestCase(DBShapesTest)

0 commit comments

Comments
 (0)