Repository: PyFilesystem/pyfilesystem2

Issue:
Free memory on MemoryFS.close
Steps to reproduce:

1. Run this piece of code in your favorite REPL.
```python
from fs.memoryfs import MemoryFS

mem_fs = MemoryFS()
with open('large_binary_file', 'rb') as f:
	mem_fs.upload('/large_binary_file', f)
mem_fs.close()
```
2. Watch python process before and after running this script.
3. The memory is there and the files are not removed.
4. Run `del mem_fs` and it immediately claims back the memory.

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/24779343/badge)](https://coveralls.io/builds/24779343)

Coverage decreased (-1.4%) to 98.226% when pulling **147d2dc7030437439532855450eee2faab793bab on dargueta:memoryfs-close** into **37b46b4a991a124f4d8e199e111ee21f00c2213b on PyFilesystem:master**.

dargueta: I know the contributors list is supposed to be sorted alphabetically but do I *really* need to put my name at the top? Feels weird.
willmcgugan: @dargueta It's cool. The idea was that the order doesn't indicate significance of contribution.

PR looks good BTW, thanks. I'll give it another review soon.
willmcgugan: Thanks

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aaf125a..b36f9a9 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 5c2a9b5..ef1d3a0 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 2446b0d..dcee49d 100644
--- a/fs/memoryfs.py
+++ b/fs/memoryfs.py
@@ -340,6 +340,11 @@ class MemoryFS(FS):
                 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 ()


Test patch:
diff --git a/tests/test_memoryfs.py b/tests/test_memoryfs.py
index 8f8adbd..aaf6e77 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),
+        )