On Thu, Jul 24, 2025 at 11:41:20AM +0100, Joe Orton wrote: > On Wed, Jul 23, 2025 at 09:08:48PM +0200, Daniel Sahlberg wrote: > > Hi, > > > > The patch below has been floating in dev@ for about a month. It looks looks > > like an improvement to me, but it is way out of my comfort zone. > > > > @Joe Orton <jor...@redhat.com> are you able to test this? > > Sure thing, I've fired off a test build against Fedora Rawhide with > Yasuhito's patch applied, will let you know how it goes.
It failed as below. I'm testing 1.14.5 with the attached patch, which brings repository.py to match trunk + Yasuhito's patch. ====================================================================== FAIL: test_replay_batons_refcounts (repository.SubversionRepositoryTestCase.test_replay_batons_refcounts) Issue SVN-4917: check ref-count of batons created and used in callbacks ---------------------------------------------------------------------- Traceback (most recent call last): File "/builddir/build/BUILD/subversion-1.14.5-build/subversion-1.14.5/subversion/bindings/swig/python/tests/repository.py", line 447, in test_replay_batons_refcounts self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3], ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "leak on baton %s after replay without errors" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ % repr(baton_tuple)) ^^^^^^^^^^^^^^^^^^^^ AssertionError: 2 != 0 : leak on baton (b'dir baton', b'', (b'', b'', 11), 0) after replay without errors ----------------------------------------------------------------------
--- subversion-1.14.5/subversion/bindings/swig/python/tests/mergeinfo.py.10 +++ subversion-1.14.5/subversion/bindings/swig/python/tests/mergeinfo.py @@ -138,8 +138,8 @@ # ....and now 3 (incref during iteration of each range object) refcount = sys.getrefcount(r) - # ....and finally, 4 (getrefcount() also increfs) - expected = 4 + # ....and finally, 4 (getrefcount() also increfs, but not as of 3.14) + expected = 3 if sys.version_info >= (3, 14) else 4 # Note: if path and index are not '/trunk' and 0 respectively, then # only some of the range objects are leaking, which is, as far as --- subversion-1.14.5/subversion/bindings/swig/python/tests/repository.py.10 +++ subversion-1.14.5/subversion/bindings/swig/python/tests/repository.py @@ -95,7 +95,9 @@ def open_root(self, base_revision, dir_pool=None): bt = repos.ChangeCollector.open_root(self, base_revision, dir_pool) - self.batons.append((b'dir baton', b'', bt, sys.getrefcount(bt))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'dir baton', b'', bt, sys.getrefcount(bt) - 1)) return bt def add_directory(self, path, parent_baton, @@ -104,14 +106,18 @@ copyfrom_path, copyfrom_revision, dir_pool) - self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt) - 1)) return bt def open_directory(self, path, parent_baton, base_revision, dir_pool=None): bt = repos.ChangeCollector.open_directory(self, path, parent_baton, base_revision, dir_pool) - self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'dir baton', path, bt, sys.getrefcount(bt) - 1)) return bt def add_file(self, path, parent_baton, @@ -119,13 +125,17 @@ bt = repos.ChangeCollector.add_file(self, path, parent_baton, copyfrom_path, copyfrom_revision, file_pool) - self.batons.append((b'file baton', path, bt, sys.getrefcount(bt))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'file baton', path, bt, sys.getrefcount(bt) - 1)) return bt def open_file(self, path, parent_baton, base_revision, file_pool=None): bt = repos.ChangeCollector.open_file(self, path, parent_baton, base_revision, file_pool) - self.batons.append((b'file baton', path, bt, sys.getrefcount(bt))) + # refcount for the object refered by bt is counted down after + # the variable bt is deleted (i.e. after leaving this function) + self.batons.append((b'file baton', path, bt, sys.getrefcount(bt) - 1)) return bt def close_edit(self, pool=None): @@ -138,7 +148,7 @@ class BatonCollectorErrorOnClose(BatonCollector): """Same as BatonCollector, but raises an Exception when close the - file/dir specfied by error_path""" + file/dir specified by error_path""" def __init__(self, fs_ptr, root, pool=None, notify_cb=None, error_path=b''): BatonCollector.__init__(self, fs_ptr, root, pool, notify_cb) self.error_path = error_path @@ -429,29 +439,35 @@ root = fs.revision_root(self.fs, self.rev) editor = BatonCollector(self.fs, root) e_ptr, e_baton = delta.make_editor(editor) + refcount_at_first = sys.getrefcount(e_ptr) repos.replay(root, e_ptr, e_baton) - for baton in editor.batons: - self.assertEqual(sys.getrefcount(baton[2]), 2, + for baton_tuple in editor.batons: + # baton_tuple: 4-tuple(baton_type: bytes, node: bytes, bt: baton, + # expected_refcount_of_bt: int) + self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3], "leak on baton %s after replay without errors" - % repr(baton)) + % repr(baton_tuple)) del e_baton - self.assertEqual(sys.getrefcount(e_ptr), 2, + self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first, "leak on editor baton after replay without errors") editor = BatonCollectorErrorOnClose(self.fs, root, error_path=b'branches/v1x') e_ptr, e_baton = delta.make_editor(editor) + refcount_at_first = sys.getrefcount(e_ptr) self.assertRaises(SubversionException, repos.replay, root, e_ptr, e_baton) batons = editor.batons # As svn_repos_replay calls neither close_edit callback nor abort_edit - # if an error has occured during processing, references of Python objects - # in decendant batons may live until e_baton is deleted. + # if an error has occurred during processing, references of Python objects + # in descendant batons may live until e_baton is deleted. del e_baton - for baton in batons: - self.assertEqual(sys.getrefcount(baton[2]), 2, + for baton_tuple in batons: + # baton_tuple: 4-tuple(baton_type: bytes, node: bytes, bt: baton, + # expected_refcount_of_bt: int) + self.assertEqual(sys.getrefcount(baton_tuple[2]), baton_tuple[3], "leak on baton %s after replay with an error" - % repr(baton)) - self.assertEqual(sys.getrefcount(e_ptr), 2, + % repr(baton_tuple)) + self.assertEqual(sys.getrefcount(e_ptr), refcount_at_first, "leak on editor baton after replay with an error") def test_delta_editor_apply_textdelta_handler_refcount(self):