From 760b2fc7ecbb158dc3d2eb51142417f04beb03e6 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 14 Jul 2014 12:18:47 -0400 Subject: [PATCH 1/3] Fix memory leak in FT2Font. --- src/ft2font.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ft2font.cpp b/src/ft2font.cpp index d54f14c5280c..58d8be8b7564 100644 --- a/src/ft2font.cpp +++ b/src/ft2font.cpp @@ -985,6 +985,10 @@ FT2Font::~FT2Font() { FT_Done_Glyph(glyphs[i]); } + + if (stream.descriptor.pointer != NULL) { + PyMem_Free(stream.descriptor.pointer); + } } int From 7b39e78b0807b8176183a5890b502f0f521b2465 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 14 Jul 2014 15:58:14 -0400 Subject: [PATCH 2/3] Fix memory leaks in the temporary rasterized images. --- src/_backend_agg.cpp | 24 +++++++++--------------- src/_image.cpp | 2 +- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index c35a9da93cbb..416a3991f4ad 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -1557,6 +1557,7 @@ RendererAgg::_draw_path_collection_generic if ((Nfacecolors == 0 && Nedgecolors == 0) || Npaths == 0) { + Py_XDECREF(transforms_arr); return Py::Object(); } @@ -1708,6 +1709,8 @@ RendererAgg::_draw_path_collection_generic } } + Py_XDECREF(transforms_arr); + return Py::Object(); } @@ -2383,11 +2386,7 @@ RendererAgg::tostring_rgba_minimized(const Py::Tuple& args) int newwidth = 0; int newheight = 0; - #if PY3K - Py::Bytes data; - #else - Py::String data; - #endif + PyObject *data; if (xmin < xmax && ymin < ymax) { @@ -2406,18 +2405,12 @@ RendererAgg::tostring_rgba_minimized(const Py::Tuple& args) // the _AsString() API. unsigned int* dst; - #if PY3K - data = Py::Bytes(static_cast(NULL), (int) newsize); - dst = reinterpret_cast(PyBytes_AsString(data.ptr())); - #else - data = Py::String(static_cast(NULL), (int) newsize); - dst = reinterpret_cast(PyString_AsString(data.ptr())); - #endif - - if (dst == NULL) + data = PyBytes_FromStringAndSize(NULL, newsize); + if (data == NULL) { throw Py::MemoryError("RendererAgg::tostring_minimized could not allocate memory"); } + dst = (unsigned int *)PyBytes_AsString(data); unsigned int* src = (unsigned int*)pixBuffer; for (int y = ymin; y < ymax; ++y) @@ -2436,7 +2429,8 @@ RendererAgg::tostring_rgba_minimized(const Py::Tuple& args) bounds[3] = Py::Int(newheight); Py::Tuple result(2); - result[0] = data; + result[0] = Py::Object(data, false); + Py_DECREF(data); result[1] = bounds; return result; diff --git a/src/_image.cpp b/src/_image.cpp index b1c59e9674ba..73f2624c772c 100644 --- a/src/_image.cpp +++ b/src/_image.cpp @@ -1264,7 +1264,7 @@ _image_module::frombuffer(const Py::Tuple& args) args.verify_length(4); - PyObject *bufin = new_reference_to(args[0]); + PyObject *bufin = args[0].ptr(); size_t x = (long)Py::Int(args[1]); size_t y = (long)Py::Int(args[2]); From 42fa2b6788af822874e9add6458e479fa707d27e Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 14 Jul 2014 16:23:33 -0400 Subject: [PATCH 3/3] Fix memory leaks in the test suite. This works by avoiding storing a reference to the figure in the "do_test" enclosure in the image_comparison decorator. nose keeps a reference to all of its test functions, and thereby holds on to every figure in every test that uses the image_comparison decorator (which is most). Instead of using the figure in the outer scope, this just uses the figure number, and then it's looked up when the test function is actually run. Also adds gc.collect() so we can ensure there are zero figures in memory at the start of each test. Probably not strictly necessary, but it makes analysing memory usage easier. --- lib/matplotlib/testing/decorators.py | 39 ++++++++++++---------------- test_only.py | 18 +++++++++++++ 2 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 test_only.py diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 21aa2664b50c..d5efd03c05a0 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -4,6 +4,7 @@ import six import functools +import gc import os import sys import shutil @@ -65,6 +66,17 @@ def failer(*args, **kwargs): return known_fail_decorator +def _do_cleanup(original_units_registry): + plt.close('all') + gc.collect() + + matplotlib.tests.setup() + + matplotlib.units.registry.clear() + matplotlib.units.registry.update(original_units_registry) + warnings.resetwarnings() # reset any warning filters set in tests + + class CleanupTest(object): @classmethod def setup_class(cls): @@ -72,13 +84,7 @@ def setup_class(cls): @classmethod def teardown_class(cls): - plt.close('all') - - matplotlib.tests.setup() - - matplotlib.units.registry.clear() - matplotlib.units.registry.update(cls.original_units_registry) - warnings.resetwarnings() # reset any warning filters set in tests + _do_cleanup(cls.original_units_registry) def test(self): self._func() @@ -93,13 +99,7 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - plt.close('all') - - matplotlib.tests.setup() - - matplotlib.units.registry.clear() - matplotlib.units.registry.update(cls.original_units_registry) - warnings.resetwarnings() # reset any warning filters set in tests + _do_cleanup(cls.original_units_registry) def cleanup(func): @@ -109,13 +109,8 @@ def wrapped_function(*args, **kwargs): try: func(*args, **kwargs) finally: - plt.close('all') - - matplotlib.tests.setup() + _do_cleanup(original_units_registry) - matplotlib.units.registry.clear() - matplotlib.units.registry.update(original_units_registry) - warnings.resetwarnings() #reset any warning filters set in tests return wrapped_function @@ -157,8 +152,6 @@ def test(self): baseline_dir, result_dir = _image_directories(self._func) for fignum, baseline in zip(plt.get_fignums(), self._baseline_images): - figure = plt.figure(fignum) - for extension in self._extensions: will_fail = not extension in comparable_formats() if will_fail: @@ -182,6 +175,8 @@ def test(self): will_fail, fail_msg, known_exception_class=ImageComparisonFailure) def do_test(): + figure = plt.figure(fignum) + if self._remove_text: self.remove_text(figure) diff --git a/test_only.py b/test_only.py new file mode 100644 index 000000000000..192881f5c629 --- /dev/null +++ b/test_only.py @@ -0,0 +1,18 @@ +from distutils.core import setup + +import os + +baseline_images = [ + 'baseline_images/%s/*' % x + for x in os.listdir('lib/matplotlib/tests/baseline_images')] + +baseline_images += [ + 'mpltest.ttf', + 'test_rcparams.rc' + ] + +setup(name='matplotlib.tests', + packages=['matplotlib.tests'], + package_dir={'matplotlib.tests': 'lib/matplotlib/tests'}, + package_data={'matplotlib.tests': baseline_images} +)