diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index d9d401a2789c0e..3813d016422b2e 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -289,6 +289,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length pagefile) will silently create a new map with the original data copied over up to the length of the new size. + Availability: Windows and systems with the ``mremap()`` system call. + .. versionchanged:: 3.11 Correctly fails if attempting to resize when another map is held Allows resize against an anonymous map on Windows diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 932bb100cbee23..3fe484646b23d7 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -692,6 +692,9 @@ Porting to Python 3.15 :func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated. (Contributed by Serhiy Storchaka in :gh:`137044`.) +* :meth:`~mmap.mmap.resize` has been removed on platforms that don't support the + underlying syscall, instead of raising a :exc:`SystemError`. + Deprecated C APIs ----------------- diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index da69770915092a..75ea1a671b67de 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -57,6 +57,7 @@ def test_basic(self): f.write(b'\0'* (PAGESIZE-3) ) f.flush() m = mmap.mmap(f.fileno(), 2 * PAGESIZE) + self.addCleanup(m.close) finally: f.close() @@ -114,31 +115,28 @@ def test_basic(self): # Try to seek to negative position... self.assertRaises(ValueError, m.seek, -len(m)-1, 2) + @unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize') + def test_resize(self): + # Create a file to be mmap'ed. + with open(TESTFN, 'bw+') as f: + # Write 2 pages worth of data to the file + f.write(b'\0'* 2 * PAGESIZE) + f.flush() + m = mmap.mmap(f.fileno(), 2 * PAGESIZE) + self.addCleanup(m.close) + # Try resizing map - try: - m.resize(512) - except SystemError: - # resize() not supported - # No messages are printed, since the output of this test suite - # would then be different across platforms. - pass - else: - # resize() is supported - self.assertEqual(len(m), 512) - # Check that we can no longer seek beyond the new size. - self.assertRaises(ValueError, m.seek, 513, 0) - - # Check that the underlying file is truncated too - # (bug #728515) - f = open(TESTFN, 'rb') - try: - f.seek(0, 2) - self.assertEqual(f.tell(), 512) - finally: - f.close() - self.assertEqual(m.size(), 512) + m.resize(512) + self.assertEqual(len(m), 512) + # Check that we can no longer seek beyond the new size. + self.assertRaises(ValueError, m.seek, 513, 0) - m.close() + # Check that the underlying file is truncated too + # (bug #728515) + with open(TESTFN, 'rb') as f: + f.seek(0, 2) + self.assertEqual(f.tell(), 512) + self.assertEqual(m.size(), 512) def test_access_parameter(self): # Test for "access" keyword parameter @@ -183,15 +181,10 @@ def test_access_parameter(self): else: self.fail("Able to write to readonly memory map") - # Ensuring that readonly mmap can't be resized - try: - m.resize(2*mapsize) - except SystemError: # resize is not universally supported - pass - except TypeError: - pass - else: - self.fail("Able to resize readonly memory map") + if hasattr(m, 'resize'): + # Ensuring that readonly mmap can't be resized + with self.assertRaises(TypeError): + m.resize(2 * mapsize) with open(TESTFN, "rb") as fp: self.assertEqual(fp.read(), b'a'*mapsize, "Readonly memory map data file was modified") @@ -242,8 +235,9 @@ def test_access_parameter(self): with open(TESTFN, "rb") as fp: self.assertEqual(fp.read(), b'c'*mapsize, "Copy-on-write test data file should not be modified.") - # Ensuring copy-on-write maps cannot be resized - self.assertRaises(TypeError, m.resize, 2*mapsize) + if hasattr(m, 'resize'): + # Ensuring copy-on-write maps cannot be resized + self.assertRaises(TypeError, m.resize, 2 * mapsize) m.close() # Ensuring invalid access parameter raises exception @@ -282,10 +276,11 @@ def test_trackfd_parameter(self, close_original_fd): self.assertEqual(len(m), size) with self.assertRaises(ValueError): m.size() - with self.assertRaises(ValueError): - m.resize(size * 2) - with self.assertRaises(ValueError): - m.resize(size // 2) + if hasattr(m, 'resize'): + with self.assertRaises(ValueError): + m.resize(size * 2) + with self.assertRaises(ValueError): + m.resize(size // 2) self.assertIs(m.closed, False) # Smoke-test other API @@ -313,8 +308,9 @@ def test_trackfd_neg1(self): with mmap.mmap(-1, size, trackfd=False) as m: with self.assertRaises(ValueError): m.size() - with self.assertRaises(ValueError): - m.resize(size // 2) + if hasattr(m, 'resize'): + with self.assertRaises(ValueError): + m.resize(size // 2) self.assertEqual(len(m), size) m[0] = ord('a') assert m[0] == ord('a') @@ -608,13 +604,9 @@ def test_offset (self): self.assertEqual(m[0:3], b'foo') f.close() - # Try resizing map - try: + if hasattr(m, 'resize'): + # Try resizing map m.resize(512) - except SystemError: - pass - else: - # resize() is supported self.assertEqual(len(m), 512) # Check that we can no longer seek beyond the new size. self.assertRaises(ValueError, m.seek, 513, 0) @@ -806,14 +798,12 @@ def test_write_returning_the_number_of_bytes_written(self): self.assertEqual(mm.write(b"yz"), 2) self.assertEqual(mm.write(b"python"), 6) + @unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize') def test_resize_past_pos(self): m = mmap.mmap(-1, 8192) self.addCleanup(m.close) m.read(5000) - try: - m.resize(4096) - except SystemError: - self.skipTest("resizing not supported") + m.resize(4096) self.assertEqual(m.read(14), b'') self.assertRaises(ValueError, m.read_byte) self.assertRaises(ValueError, m.write_byte, 42) @@ -895,6 +885,7 @@ def test_madvise(self): self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, 2), None) self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, size), None) + @unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize') def test_resize_up_anonymous_mapping(self): """If the mmap is backed by the pagefile ensure a resize up can happen and that the original data is still in place @@ -911,16 +902,13 @@ def test_resize_up_anonymous_mapping(self): with self.assertRaises(ValueError): m.resize(new_size) else: - try: - m.resize(new_size) - except SystemError: - pass - else: - self.assertEqual(len(m), new_size) - self.assertEqual(m[:start_size], data) - self.assertEqual(m[start_size:], b'\0' * (new_size - start_size)) + m.resize(new_size) + self.assertEqual(len(m), new_size) + self.assertEqual(m[:start_size], data) + self.assertEqual(m[start_size:], b'\0' * (new_size - start_size)) @unittest.skipUnless(os.name == 'posix', 'requires Posix') + @unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize') def test_resize_up_private_anonymous_mapping(self): start_size = PAGESIZE new_size = 2 * start_size @@ -928,15 +916,12 @@ def test_resize_up_private_anonymous_mapping(self): with mmap.mmap(-1, start_size, flags=mmap.MAP_PRIVATE) as m: m[:] = data - try: - m.resize(new_size) - except SystemError: - pass - else: - self.assertEqual(len(m), new_size) - self.assertEqual(m[:start_size], data) - self.assertEqual(m[start_size:], b'\0' * (new_size - start_size)) + m.resize(new_size) + self.assertEqual(len(m), new_size) + self.assertEqual(m[:start_size], data) + self.assertEqual(m[start_size:], b'\0' * (new_size - start_size)) + @unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize') def test_resize_down_anonymous_mapping(self): """If the mmap is backed by the pagefile ensure a resize down up can happen and that a truncated form of the original data is still in place @@ -947,17 +932,13 @@ def test_resize_down_anonymous_mapping(self): with mmap.mmap(-1, start_size) as m: m[:] = data - try: - m.resize(new_size) - except SystemError: - pass - else: - self.assertEqual(len(m), new_size) - self.assertEqual(m[:], data[:new_size]) - if sys.platform.startswith(('linux', 'android')): - # Can't expand to its original size. - with self.assertRaises(ValueError): - m.resize(start_size) + m.resize(new_size) + self.assertEqual(len(m), new_size) + self.assertEqual(m[:], data[:new_size]) + if sys.platform.startswith(('linux', 'android')): + # Can't expand to its original size. + with self.assertRaises(ValueError): + m.resize(start_size) @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_resize_fails_if_mapping_held_elsewhere(self): diff --git a/Misc/NEWS.d/next/Library/2025-08-31-12-34-02.gh-issue-138205.iHXb1z.rst b/Misc/NEWS.d/next/Library/2025-08-31-12-34-02.gh-issue-138205.iHXb1z.rst new file mode 100644 index 00000000000000..0dd94324ffd2ea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-31-12-34-02.gh-issue-138205.iHXb1z.rst @@ -0,0 +1,2 @@ +Removed the :meth:`~mmap.mmap.resize` method on platforms that don't support the +underlying syscall, instead of raising a :exc:`SystemError`. diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index dcaadb818e0bf7..8c9d463ad903ec 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -628,6 +628,7 @@ is_writable(mmap_object *self) return 0; } +#if defined(MS_WINDOWS) || defined(HAVE_MREMAP) static int is_resizeable(mmap_object *self) { @@ -648,6 +649,7 @@ is_resizeable(mmap_object *self) return 0; } +#endif /* MS_WINDOWS || HAVE_MREMAP */ static PyObject * @@ -766,6 +768,7 @@ mmap_size_method(PyObject *op, PyObject *Py_UNUSED(ignored)) / new size? */ +#if defined(MS_WINDOWS) || defined(HAVE_MREMAP) static PyObject * mmap_resize_method(PyObject *op, PyObject *args) { @@ -880,11 +883,6 @@ mmap_resize_method(PyObject *op, PyObject *args) #endif /* MS_WINDOWS */ #ifdef UNIX -#ifndef HAVE_MREMAP - PyErr_SetString(PyExc_SystemError, - "mmap: resizing not available--no mremap()"); - return NULL; -#else void *newmap; #ifdef __linux__ @@ -916,10 +914,10 @@ mmap_resize_method(PyObject *op, PyObject *args) self->data = newmap; self->size = new_size; Py_RETURN_NONE; -#endif /* HAVE_MREMAP */ #endif /* UNIX */ } } +#endif /* MS_WINDOWS || HAVE_MREMAP */ static PyObject * mmap_tell_method(PyObject *op, PyObject *Py_UNUSED(ignored)) @@ -1207,7 +1205,9 @@ static struct PyMethodDef mmap_object_methods[] = { {"read", mmap_read_method, METH_VARARGS}, {"read_byte", mmap_read_byte_method, METH_NOARGS}, {"readline", mmap_read_line_method, METH_NOARGS}, +#if defined(MS_WINDOWS) || defined(HAVE_MREMAP) {"resize", mmap_resize_method, METH_VARARGS}, +#endif {"seek", mmap_seek_method, METH_VARARGS}, {"seekable", mmap_seekable_method, METH_NOARGS}, {"size", mmap_size_method, METH_NOARGS},