diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf125a9..b36f9a95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [2.4.9] - (Unreleased) + +### Changed + +- `MemFS` now immediately releases all memory it holds when `close()` is called, + rather than when it gets garbage collected. Closes [issue #308](https://github.com/PyFilesystem/pyfilesystem2/issues/308). + ## [2.4.8] - 2019-06-12 ### Changed diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5c2a9b54..ef1d3a09 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -2,6 +2,7 @@ Many thanks to the following developers for contributing to this project: +- [Diego Argueta](https://github.com/dargueta) - [Geoff Jukes](https://github.com/geoffjukes) - [Giampaolo](https://github.com/gpcimino) - [Martin Larralde](https://github.com/althonos) diff --git a/fs/memoryfs.py b/fs/memoryfs.py index 2446b0d1..dcee49db 100644 --- a/fs/memoryfs.py +++ b/fs/memoryfs.py @@ -340,6 +340,11 @@ def _get_dir_entry(self, dir_path): current_entry = current_entry.get_entry(path_component) return current_entry + def close(self): + # type: () -> None + self.root = None + return super(MemoryFS, self).close() + def getinfo(self, path, namespaces=None): # type: (Text, Optional[Collection[Text]]) -> Info namespaces = namespaces or () diff --git a/tests/test_memoryfs.py b/tests/test_memoryfs.py index 8f8adbd1..aaf6e779 100644 --- a/tests/test_memoryfs.py +++ b/tests/test_memoryfs.py @@ -1,9 +1,17 @@ from __future__ import unicode_literals +import posixpath import unittest from fs import memoryfs from fs.test import FSTestCases +from fs.test import UNICODE_TEXT + +try: + # Only supported on Python 3.4+ + import tracemalloc +except ImportError: + tracemalloc = None class TestMemoryFS(FSTestCases, unittest.TestCase): @@ -11,3 +19,50 @@ class TestMemoryFS(FSTestCases, unittest.TestCase): def make_fs(self): return memoryfs.MemoryFS() + + def _create_many_files(self): + for parent_dir in {"/", "/one", "/one/two", "/one/other-two/three"}: + self.fs.makedirs(parent_dir, recreate=True) + for file_id in range(50): + self.fs.writetext( + posixpath.join(parent_dir, str(file_id)), UNICODE_TEXT + ) + + @unittest.skipIf( + not tracemalloc, "`tracemalloc` isn't supported on this Python version." + ) + def test_close_mem_free(self): + """Ensure all file memory is freed when calling close(). + + Prevents regression against issue #308. + """ + trace_filters = [tracemalloc.Filter(True, "*/memoryfs.py")] + tracemalloc.start() + + before = tracemalloc.take_snapshot().filter_traces(trace_filters) + self._create_many_files() + after_create = tracemalloc.take_snapshot().filter_traces(trace_filters) + + self.fs.close() + after_close = tracemalloc.take_snapshot().filter_traces(trace_filters) + tracemalloc.stop() + + [diff_create] = after_create.compare_to( + before, key_type="filename", cumulative=True + ) + self.assertGreater( + diff_create.size_diff, + 0, + "Memory usage didn't increase after creating files; diff is %0.2f KiB." + % (diff_create.size_diff / 1024.0), + ) + + [diff_close] = after_close.compare_to( + after_create, key_type="filename", cumulative=True + ) + self.assertLess( + diff_close.size_diff, + 0, + "Memory usage increased after closing the file system; diff is %0.2f KiB." + % (diff_close.size_diff / 1024.0), + )