Index: subversion/bindings/swig/core.i =================================================================== --- subversion/bindings/swig/core.i (revision 15359) +++ subversion/bindings/swig/core.i (working copy) @@ -551,6 +551,11 @@ #endif #ifdef SWIGPYTHON + +void svn_swig_py_set_application_pool(apr_pool_t *pool); +void svn_swig_py_clear_application_pool(); +apr_pool_t *svn_swig_py_get_application_pool(); + %init %{ /* This is a hack. I dunno if we can count on SWIG calling the module "m" */ PyModule_AddObject(m, "SubversionException", Index: subversion/bindings/swig/python/tests/pool.py =================================================================== --- subversion/bindings/swig/python/tests/pool.py (revision 0) +++ subversion/bindings/swig/python/tests/pool.py (revision 0) @@ -0,0 +1,104 @@ +from svn.core import * +import svn.core +import libsvn.core +import unittest +import weakref + +# Test case for the new automatic pool management infrastructure + +class PoolTestCase(unittest.TestCase): + + def test_pool(self): + # Create pools + parent_pool = Pool() + parent_pool_ref = weakref.ref(parent_pool) + pool = Pool(Pool(parent_pool)) + pool = Pool(pool) + + # Make sure proper exceptions are raised with incorrect input + self.assertRaises(TypeError, lambda: Pool("abcd")); + + # Check that garbage collection is working OK + self.assertTrue(parent_pool_ref()) + del parent_pool + self.assertTrue(parent_pool_ref()) + pool.clear() + newpool = libsvn.core.svn_pool_create(pool) + libsvn.core.apr_pool_destroy(newpool) + self.assertTrue(newpool) + pool.clear() + self.assertTrue(parent_pool_ref()) + del pool + self.assertFalse(parent_pool_ref()) + + # Make sure anonymous pools are destroyed properly + anonymous_pool_ref = weakref.ref(Pool()) + self.assertFalse(anonymous_pool_ref()) + + def test_compatibility_layer(self): + # Create a new pool + pool = Pool() + parent_pool_ref = weakref.ref(pool) + pool = svn_pool_create(Pool(pool)) + pool_ref = weakref.ref(pool) + + # Make sure proper exceptions are raised with incorrect input + self.assertRaises(TypeError, lambda: svn_pool_create("abcd")); + + # Test whether pools are destroyed properly + pool = svn_pool_create(pool) + self.assertTrue(pool_ref()) + self.assertTrue(parent_pool_ref()) + del pool + self.assertFalse(pool_ref()) + self.assertFalse(parent_pool_ref()) + + # Ensure that AssertionErrors are raised when a pool is deleted twice + newpool = Pool() + newpool2 = Pool(newpool) + svn_pool_clear(newpool) + self.assertRaises(AssertionError, lambda: libsvn.core.apr_pool_destroy(newpool2)) + self.assertRaises(AssertionError, lambda: svn_pool_destroy(newpool2)); + svn_pool_destroy(newpool) + self.assertRaises(AssertionError, lambda: svn_pool_destroy(newpool)) + + # Try to allocate memory from a destroyed pool + self.assertRaises(AssertionError, lambda: svn_pool_create(newpool)) + + # Create and destroy a pool + svn_pool_destroy(svn_pool_create()) + + # Make sure anonymous pools are destroyed properly + anonymous_pool_ref = weakref.ref(svn_pool_create()) + self.assertFalse(anonymous_pool_ref()) + + # Try to cause a segfault using apr_terminate + apr_terminate() + apr_initialize() + apr_terminate() + apr_terminate() + + # Destroy the application pool + svn_pool_destroy(svn.core.application_pool) + + # Double check that the application pool has been deleted + self.assertFalse(svn.core.application_pool) + + # Try to allocate memory from the old application pool + self.assertRaises(AssertionError, lambda: svn_pool_create(application_pool)); + + # Bring the application pool back to life + svn_pool_create() + + # Double check that the application pool has been created + self.assertTrue(svn.core.application_pool) + + # We can still destroy and create pools at will + svn_pool_destroy(svn_pool_create()) + +def suite(): + return unittest.makeSuite(PoolTestCase, 'test') + +if __name__ == '__main__': + runner = unittest.TextTestRunner() + runner.run(suite()); Index: subversion/bindings/swig/python/tests/run_all.py =================================================================== --- subversion/bindings/swig/python/tests/run_all.py (revision 0) +++ subversion/bindings/swig/python/tests/run_all.py (revision 0) @@ -0,0 +1,13 @@ +import unittest +import pool + +# Run all tests + +def suite(): + """Run all tests""" + suite = unittest.TestSuite() + suite.addTest(pool.suite()) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') Index: subversion/bindings/swig/python/svn/core.py =================================================================== --- subversion/bindings/swig/python/svn/core.py (revision 15359) +++ subversion/bindings/swig/python/svn/core.py (working copy) @@ -17,33 +17,97 @@ ###################################################################### from libsvn.core import * +import libsvn.core as _core def _unprefix_names(symbol_dict, from_prefix, to_prefix = ''): for name, value in symbol_dict.items(): if name.startswith(from_prefix): symbol_dict[to_prefix + name[len(from_prefix):]] = value -# some minor patchups -svn_pool_destroy = apr_pool_destroy -svn_pool_clear = apr_pool_clear -def run_app(func, *args, **kw): - '''Run a function as an "APR application". +application_pool = None - APR is initialized, and an application pool is created. Cleanup is - performed as the function exits (normally or via an exception. - ''' - apr_initialize() - try: - pool = svn_pool_create(None) - try: - return apply(func, (pool,) + args, kw) - finally: - svn_pool_destroy(pool) - finally: - apr_terminate() +class Pool(object): + """A Pythonic memory pool object""" + def __init__(self, parent_pool=None): + """Create a new memory pool""" + global application_pool + + # Create pool + self._parent_pool = parent_pool or application_pool + self._pool = _core.svn_pool_create(self._parent_pool) + self._clears = 0; + + # Protect _core from GC + self._core = _core + + # If we have a parent, write down its current status + if self._parent_pool: + self._parent_clears = self._parent_pool._clears + else: + # If we are an application-level pool, + # then initialize APR and set this pool + # to be the application-level pool + _core.apr_initialize() + svn_swig_py_set_application_pool(self) + + application_pool = self + + def valid(self): + """Check whether this memory pool and its parents + are still valid""" + return (hasattr(self,"_pool") and ( + self._parent_pool is None or + (self._parent_clears == self._parent_pool._clears and + self._parent_pool.valid()))) + + def assert_valid(self): + """Assert that this memory_pool is still valid.""" + assert self.valid(); + + def clear(self): + """Clear embedded memory pool. Invalidate all subpools.""" + self._core.apr_pool_clear(self) + self._clears = self._clears + 1; + + def destroy(self): + """Destroy embedded memory pool. If you do not destroy + the memory pool manually, Python will destroy it + automatically.""" + + global application_pool + + self.assert_valid() + + # Destroy pool + self._core.apr_pool_destroy(self) + del self._pool + + # Clear application pool and terminate APR if necessary + if not self._parent_pool: + application_pool = None + svn_swig_py_clear_application_pool() + self._core.apr_terminate() + + # Free up memory + del self._parent_pool + del self._core + + def __del__(self): + """Automatically destroy memory pools, if necessary""" + if self.valid(): + self.destroy() + +# Initialize application-level pool +Pool() + +# Hide raw pool management functions. +# If you still want to use these, use libsvn.core instead. +del apr_pool_destroy +del apr_pool_clear + def svn_path_compare_paths(path1, path2): path1_len = len (path1); path2_len = len (path2); @@ -166,3 +230,60 @@ return _string.join(map(escape_shell_arg, argv), " ") # ============================================================================ +# Deprecated functions + +def apr_initialize(): + """Deprecated. APR is now initialized automatically. This is + a compatibility wrapper providing the interface of the + Subversion 1.2.x and earlier bindings.""" + pass + +def apr_terminate(): + """Deprecated. APR is now terminated automatically. This is + a compatibility wrapper providing the interface of the + Subversion 1.2.x and earlier bindings.""" + pass + +def svn_pool_create(parent_pool=None): + """Deprecated. Use Pool() instead. This is a compatibility + wrapper providing the interface of the Subversion 1.2.x and + earlier bindings.""" + return Pool(parent_pool) + +def svn_pool_destroy(pool): + """Deprecated. Pools are now destroyed automatically. If you + want to manually destroy a pool, use Pool.destroy. This is + a compatibility wrapper providing the interface of the + Subversion 1.2.x and earlier bindings.""" + + assert pool is not None + + if hasattr(pool,"destroy"): + pool.destroy() + else: + _core.apr_pool_destroy(pool) + +def svn_pool_clear(pool): + """Deprecated. Use Pool.clear instead. This is a compatibility + wrapper providing the interface of the Subversion 1.2.x and + earlier bindings.""" + + assert pool is not None + + if hasattr(pool,"clear"): + pool.clear() + else: + _core.apr_pool_clear(pool) + +def run_app(func, *args, **kw): + '''Deprecated: Application-level pools are now created + automatically. APR is also initialized and terminated + automatically. This is a compatibility wrapper providing the + interface of the Subversion 1.2.x and earlier bindings. + + Run a function as an "APR application". + + APR is initialized, and an application pool is created. Cleanup is + performed as the function exits (normally or via an exception). + ''' + return apply(func, (application_pool,) + args, kw) Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c =================================================================== --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c (revision 15359) +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c (working copy) @@ -109,6 +109,61 @@ +/*** Automatic Pool Management Functions ***/ + +/* The application pool */ +apr_pool_t *_global_pool = NULL; +static char *poolAttribute = (char *) "_pool"; +static char *assertValid = (char *) "assert_valid"; +static char *emptyTuple = (char *) "()"; + + +/* Set the application pool */ +void svn_swig_py_set_application_pool(apr_pool_t *pool) +{ + _global_pool = pool; +} + +/* Clear the application pool */ +void svn_swig_py_clear_application_pool() +{ + _global_pool = NULL; +} + +/* Get the application pool */ +apr_pool_t *svn_swig_py_get_application_pool() +{ + return _global_pool; +} + +/* Get a pool from the argument tuple. + * If no such pool is found, use the application pool. + */ +void svn_swig_py_convert_pool(PyObject *input, apr_pool_t **pool) +{ + /* Look for pool */ + if (PyObject_HasAttrString(input, assertValid)) + { + /* Double check that the pool is valid */ + PyObject *result = PyObject_CallMethod(input, assertValid, emptyTuple); + if (result == NULL) + { + return; + } + Py_DECREF(result); + /* Get the pool */ + input = PyObject_GetAttrString(input, poolAttribute); + } + + /* Convert Pointer */ + SWIG_ConvertPtr(input, (void **)pool, + SWIG_TypeQuery("apr_pool_t *"), 1); + if (*pool == NULL) + { + *pool = _global_pool; + } +} + /*** Custom SubversionException stuffs. ***/ /* Global SubversionException class object. */ Index: subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h =================================================================== --- subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h (revision 15359) +++ subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h (working copy) @@ -77,6 +77,24 @@ void svn_swig_py_acquire_py_lock(void); +/*** Automatic Pool Management Functions ***/ +extern apr_pool_t *_global_pool; + +/* Set the application pool */ +void svn_swig_py_set_application_pool(apr_pool_t *pool); + +/* Clear the application pool */ +void svn_swig_py_clear_application_pool(void); + +/* Get the application pool */ +apr_pool_t *svn_swig_py_get_application_pool(void); + +/* Get a pool from the argument tuple. + * If no such pool is found, use the application pool. + */ +void svn_swig_py_convert_pool(PyObject *args, apr_pool_t **pool); + + /*** Functions to expose a custom SubversionException ***/ /* register a new subversion exception class */ Index: subversion/bindings/swig/svn_types.i =================================================================== --- subversion/bindings/swig/svn_types.i (revision 15359) +++ subversion/bindings/swig/svn_types.i (working copy) @@ -238,10 +238,15 @@ Define a generic arginit mapping for pools. */ -%typemap(python, arginit) apr_pool_t *pool(apr_pool_t *_global_pool) { - /* Assume that the pool here is the last argument in the list */ - SWIG_ConvertPtr(PyTuple_GET_ITEM(args, PyTuple_GET_SIZE(args) - 1), - (void **)&$1, $1_descriptor, SWIG_POINTER_EXCEPTION | 0); +%typemap(python, default) apr_pool_t *(apr_pool_t *_global_pool) { + _global_pool = $1 = svn_swig_py_get_application_pool(); +} + +%typemap(python, in) apr_pool_t * { + svn_swig_py_convert_pool($input, &$1); + if (PyErr_Occurred()) { + return NULL; + } _global_pool = $1; }