On Sat, May 23, 2020 at 2:17 PM Yasuhito FUTATSUKI <futat...@poem.co.jp> wrote: > > On 2020/05/23 12:16, Daniel Shahaf wrote: > > Yasuhito FUTATSUKI wrote on Sat, 23 May 2020 11:08 +0900: > >> I think it can be fixed by overriding _global_py_pool to $input in "in" > >> typemap, when $1 is updated. It assumes that there are no APIs that > >> uses two or more result_pool like apr_pool_t * argument for allocationg > >> return value. > > > > What about svn_repos_node_editor()? It has two pools, and if I'm > > reading the docstring correctly, "pool" will be used both for temporary > > allocations and for allocating *editor and *edit_baton, while > > "node_pool" will be used to allocate the tree that function constructs. > > > > That function is from 1.0 [according to its docstring], so it precedes > > the result_pool/scratch_pool convention by several years. > > We can't add reference of "node_pool" Python object in apr_pool_t * > members of the *editor and the *edit_baton C structure, at least > with current wrapper mechanism. I think all we can do is to make > "_parent_pool" attribute of wrapper objects for *editor and > *edit_baton point to "pool" object. > > Fortunately as apr_pool_t *node_pool is used this API only, we can > add typemap for it safely, in this case.
Now that the "unable to load *.pyd files" is fixed on trunk in r1884642 (and nominated for backport to 1.14.x), I've retried this latest patch for fixing the refcount issue. This patch makes the swig-python tests succeed for me (on Windows with Python 3.9.1, in debug configuration). So, as far as I'm concerned, this is good to go, and certainly a step forward again :-). I don't feel up to doing a proper review though, it that's what's asked here. Can Jun or Daniel perhaps take another look? -- Johan
Handle correctly multiple apr_pool_t * arguments of function in Python bindings to fix negative ref count of _global_py_pool object. * subversion/bindings/swig/include/svn_types.swg (%typemap(default) apr_pool_t *): Retrieve apr_pool_t * pointer from args parameter only when _global_pool is NULL in order to increase correctly ref count. (%typemap(in) apr_pool_t *): Retrieve apr_pool_t * pointer from the Python object and override _global_py_pool when an argument is not the last apr_pool_t * argument. (%typemap(in) apr_pool_t *scratch_pool): New typemap. (%typemap(freearg) apr_pool_t *): Decreament ref count of Python object only once. (%typemap(in) apr_pool_t *node_pool): New typemap, only for svn_repos_node_editor(). * subversion/bindings/swig/python/tests/client.py (test_inherited_props): Add tests which TypeError raises when passing non apr_pool_t * object to function which has multiple apr_pool_t * arguments. (test_conflict): Add tests that the _parent_pool attribute of the result wrapper object doesn't point scratch pool object but points result pool object. Patch by: jun66j5 (tewaked by futatuki) Index: subversion/bindings/swig/include/svn_types.swg =================================================================== --- subversion/bindings/swig/include/svn_types.swg (revision 1877963) +++ subversion/bindings/swig/include/svn_types.swg (working copy) @@ -554,23 +554,75 @@ %typemap(default, noblock=1) apr_pool_t *(apr_pool_t *_global_pool = NULL, PyObject *_global_py_pool = NULL) { - if (svn_swig_py_get_pool_arg(args, $descriptor, - &_global_py_pool, &_global_pool)) + if (_global_pool == NULL && + svn_swig_py_get_pool_arg(args, $descriptor, &_global_py_pool, + &_global_pool)) SWIG_fail; $1 = _global_pool; } %typemap(in, noblock=1) apr_pool_t * { - /* Verify that the user supplied a valid pool */ - if ($input != Py_None && $input != _global_py_pool) { - SWIG_Python_TypeError(SWIG_TypePrettyName($descriptor), $input); - SWIG_arg_fail($svn_argnum); - SWIG_fail; - } + if ($input != Py_None && $input != _global_py_pool) + { + apr_pool_t *ptr; + if (svn_swig_py_convert_ptr($input, (void **)&ptr, $descriptor) == 0) + { + $1 = ptr; + } + else + { + SWIG_Python_TypeError(SWIG_TypePrettyName($descriptor), $input); + SWIG_arg_fail($svn_argnum); + SWIG_fail; + } + Py_DECREF(_global_py_pool); + _global_py_pool = $input; + Py_INCREF(_global_py_pool); + } } +/* scratch_pool is always set by "default" typemap, however type check + * is still needed. + */ +%typemap(in, noblock=1) apr_pool_t *scratch_pool { + if ($input != Py_None) + { + apr_pool_t *ptr; + if (svn_swig_py_convert_ptr($input, (void **)&ptr, $descriptor) != 0) + { + SWIG_Python_TypeError(SWIG_TypePrettyName($descriptor), $input); + SWIG_arg_fail($svn_argnum); + SWIG_fail; + } + } +} + +/* A special typemap for svn_repos_node_editor(). In this API, parent pool + * object of the return values should be "pool" object. + */ +%typemap(in, noblock=1) apr_pool_t *node_pool { + if ($input != Py_None && $input != _global_py_pool) + { + apr_pool_t *ptr; + if (svn_swig_py_convert_ptr($input, (void **)&ptr, $descriptor) == 0) + { + $1 = ptr; + } + else + { + SWIG_Python_TypeError(SWIG_TypePrettyName($descriptor), $input); + SWIG_arg_fail($svn_argnum); + SWIG_fail; + } + } +} + %typemap(freearg) apr_pool_t * { - Py_XDECREF(_global_py_pool); + if (_global_py_pool != NULL) + { + Py_XDECREF(_global_py_pool); + _global_py_pool = NULL; + } } #endif Index: subversion/bindings/swig/python/tests/client.py =================================================================== --- subversion/bindings/swig/python/tests/client.py (revision 1877963) +++ subversion/bindings/swig/python/tests/client.py (working copy) @@ -477,6 +477,10 @@ def test_inherited_props(self): """Test inherited props""" + pool = core.Pool() + result_pool = core.Pool(pool) + scratch_pool = core.Pool(pool) + trunk_url = self.repos_uri + b'/trunk' client.propset_remote(b'svn:global-ignores', b'*.q', trunk_url, False, 12, {}, None, self.client_ctx) @@ -483,6 +487,12 @@ head = core.svn_opt_revision_t() head.kind = core.svn_opt_revision_head + self.assertRaises(TypeError, client.propget5, b'svn:global-ignores', + trunk_url, head, head, core.svn_depth_infinity, None, + self.client_ctx, 42) + self.assertRaises(TypeError, client.propget5, b'svn:global-ignores', + trunk_url, head, head, core.svn_depth_infinity, None, + self.client_ctx, None, 42) props, iprops, rev = client.propget5(b'svn:global-ignores', trunk_url, head, head, core.svn_depth_infinity, None, self.client_ctx) @@ -491,7 +501,8 @@ dir1_url = trunk_url + b'/dir1' props, iprops, rev = client.propget5(b'svn:global-ignores', dir1_url, head, head, core.svn_depth_infinity, - None, self.client_ctx) + None, self.client_ctx, result_pool, + scratch_pool) self.assertEqual(iprops[trunk_url], {b'svn:global-ignores':b'*.q\n'}) self.proplist_receiver_trunk_calls = 0 @@ -592,10 +603,15 @@ client.update4((path,), rev, core.svn_depth_unknown, True, False, False, False, False, self.client_ctx) - pool = core.Pool() - conflict = client.conflict_get(trunk_path, self.client_ctx, pool) + # New pools, only for examination where the result comes from. + result_pool = core.Pool() + scratch_pool = core.Pool() + conflict = client.conflict_get(trunk_path, self.client_ctx, + result_pool, scratch_pool) self.assertTrue(isinstance(conflict, client.svn_client_conflict_t)) + self.assertNotEqual(conflict._parent_pool, scratch_pool) + self.assertEqual(conflict._parent_pool, result_pool) conflict_opts = client.conflict_tree_get_resolution_options(conflict, self.client_ctx) @@ -602,8 +618,6 @@ self.assertTrue(isinstance(conflict_opts, list)) self.assert_all_instances_of(conflict_opts, client.svn_client_conflict_option_t) - pool.clear() - @unittest.skip("experimental API, not currently exposed") def test_shelf(self): """Test shelf api."""