gh-138205: Remove the resize method on mmap object on platforms don't support it (#138276)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
AN Long 2025-09-06 17:40:04 +09:00 committed by GitHub
parent 8554c0917e
commit c919d02ede
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 71 additions and 83 deletions

View file

@ -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 pagefile) will silently create a new map with the original data copied over
up to the length of the new size. up to the length of the new size.
Availability: Windows and systems with the ``mremap()`` system call.
.. versionchanged:: 3.11 .. versionchanged:: 3.11
Correctly fails if attempting to resize when another map is held Correctly fails if attempting to resize when another map is held
Allows resize against an anonymous map on Windows Allows resize against an anonymous map on Windows

View file

@ -701,6 +701,9 @@ Porting to Python 3.15
:func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated. :func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated.
(Contributed by Serhiy Storchaka in :gh:`137044`.) (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 Deprecated C APIs
----------------- -----------------

View file

@ -57,6 +57,7 @@ def test_basic(self):
f.write(b'\0'* (PAGESIZE-3) ) f.write(b'\0'* (PAGESIZE-3) )
f.flush() f.flush()
m = mmap.mmap(f.fileno(), 2 * PAGESIZE) m = mmap.mmap(f.fileno(), 2 * PAGESIZE)
self.addCleanup(m.close)
finally: finally:
f.close() f.close()
@ -114,31 +115,28 @@ def test_basic(self):
# Try to seek to negative position... # Try to seek to negative position...
self.assertRaises(ValueError, m.seek, -len(m)-1, 2) 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 resizing map
try: m.resize(512)
m.resize(512) self.assertEqual(len(m), 512)
except SystemError: # Check that we can no longer seek beyond the new size.
# resize() not supported self.assertRaises(ValueError, m.seek, 513, 0)
# 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 # Check that the underlying file is truncated too
# (bug #728515) # (bug #728515)
f = open(TESTFN, 'rb') with open(TESTFN, 'rb') as f:
try: f.seek(0, 2)
f.seek(0, 2) self.assertEqual(f.tell(), 512)
self.assertEqual(f.tell(), 512) self.assertEqual(m.size(), 512)
finally:
f.close()
self.assertEqual(m.size(), 512)
m.close()
def test_access_parameter(self): def test_access_parameter(self):
# Test for "access" keyword parameter # Test for "access" keyword parameter
@ -183,15 +181,10 @@ def test_access_parameter(self):
else: else:
self.fail("Able to write to readonly memory map") self.fail("Able to write to readonly memory map")
# Ensuring that readonly mmap can't be resized if hasattr(m, 'resize'):
try: # Ensuring that readonly mmap can't be resized
m.resize(2*mapsize) with self.assertRaises(TypeError):
except SystemError: # resize is not universally supported m.resize(2 * mapsize)
pass
except TypeError:
pass
else:
self.fail("Able to resize readonly memory map")
with open(TESTFN, "rb") as fp: with open(TESTFN, "rb") as fp:
self.assertEqual(fp.read(), b'a'*mapsize, self.assertEqual(fp.read(), b'a'*mapsize,
"Readonly memory map data file was modified") "Readonly memory map data file was modified")
@ -242,8 +235,9 @@ def test_access_parameter(self):
with open(TESTFN, "rb") as fp: with open(TESTFN, "rb") as fp:
self.assertEqual(fp.read(), b'c'*mapsize, self.assertEqual(fp.read(), b'c'*mapsize,
"Copy-on-write test data file should not be modified.") "Copy-on-write test data file should not be modified.")
# Ensuring copy-on-write maps cannot be resized if hasattr(m, 'resize'):
self.assertRaises(TypeError, m.resize, 2*mapsize) # Ensuring copy-on-write maps cannot be resized
self.assertRaises(TypeError, m.resize, 2 * mapsize)
m.close() m.close()
# Ensuring invalid access parameter raises exception # Ensuring invalid access parameter raises exception
@ -282,10 +276,11 @@ def test_trackfd_parameter(self, close_original_fd):
self.assertEqual(len(m), size) self.assertEqual(len(m), size)
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
m.size() m.size()
with self.assertRaises(ValueError): if hasattr(m, 'resize'):
m.resize(size * 2) with self.assertRaises(ValueError):
with self.assertRaises(ValueError): m.resize(size * 2)
m.resize(size // 2) with self.assertRaises(ValueError):
m.resize(size // 2)
self.assertIs(m.closed, False) self.assertIs(m.closed, False)
# Smoke-test other API # Smoke-test other API
@ -313,8 +308,9 @@ def test_trackfd_neg1(self):
with mmap.mmap(-1, size, trackfd=False) as m: with mmap.mmap(-1, size, trackfd=False) as m:
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
m.size() m.size()
with self.assertRaises(ValueError): if hasattr(m, 'resize'):
m.resize(size // 2) with self.assertRaises(ValueError):
m.resize(size // 2)
self.assertEqual(len(m), size) self.assertEqual(len(m), size)
m[0] = ord('a') m[0] = ord('a')
assert m[0] == ord('a') assert m[0] == ord('a')
@ -608,13 +604,9 @@ def test_offset (self):
self.assertEqual(m[0:3], b'foo') self.assertEqual(m[0:3], b'foo')
f.close() f.close()
# Try resizing map if hasattr(m, 'resize'):
try: # Try resizing map
m.resize(512) m.resize(512)
except SystemError:
pass
else:
# resize() is supported
self.assertEqual(len(m), 512) self.assertEqual(len(m), 512)
# Check that we can no longer seek beyond the new size. # Check that we can no longer seek beyond the new size.
self.assertRaises(ValueError, m.seek, 513, 0) 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"yz"), 2)
self.assertEqual(mm.write(b"python"), 6) self.assertEqual(mm.write(b"python"), 6)
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
def test_resize_past_pos(self): def test_resize_past_pos(self):
m = mmap.mmap(-1, 8192) m = mmap.mmap(-1, 8192)
self.addCleanup(m.close) self.addCleanup(m.close)
m.read(5000) m.read(5000)
try: m.resize(4096)
m.resize(4096)
except SystemError:
self.skipTest("resizing not supported")
self.assertEqual(m.read(14), b'') self.assertEqual(m.read(14), b'')
self.assertRaises(ValueError, m.read_byte) self.assertRaises(ValueError, m.read_byte)
self.assertRaises(ValueError, m.write_byte, 42) 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, 2), None)
self.assertEqual(m.madvise(mmap.MADV_NORMAL, 0, size), 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): def test_resize_up_anonymous_mapping(self):
"""If the mmap is backed by the pagefile ensure a resize up can happen """If the mmap is backed by the pagefile ensure a resize up can happen
and that the original data is still in place and that the original data is still in place
@ -911,16 +902,13 @@ def test_resize_up_anonymous_mapping(self):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
m.resize(new_size) m.resize(new_size)
else: else:
try: m.resize(new_size)
m.resize(new_size) self.assertEqual(len(m), new_size)
except SystemError: self.assertEqual(m[:start_size], data)
pass self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
else:
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(os.name == 'posix', 'requires Posix')
@unittest.skipUnless(hasattr(mmap.mmap, 'resize'), 'requires mmap.resize')
def test_resize_up_private_anonymous_mapping(self): def test_resize_up_private_anonymous_mapping(self):
start_size = PAGESIZE start_size = PAGESIZE
new_size = 2 * start_size 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: with mmap.mmap(-1, start_size, flags=mmap.MAP_PRIVATE) as m:
m[:] = data m[:] = data
try: m.resize(new_size)
m.resize(new_size) self.assertEqual(len(m), new_size)
except SystemError: self.assertEqual(m[:start_size], data)
pass self.assertEqual(m[start_size:], b'\0' * (new_size - start_size))
else:
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): def test_resize_down_anonymous_mapping(self):
"""If the mmap is backed by the pagefile ensure a resize down up can happen """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 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: with mmap.mmap(-1, start_size) as m:
m[:] = data m[:] = data
try: m.resize(new_size)
m.resize(new_size) self.assertEqual(len(m), new_size)
except SystemError: self.assertEqual(m[:], data[:new_size])
pass if sys.platform.startswith(('linux', 'android')):
else: # Can't expand to its original size.
self.assertEqual(len(m), new_size) with self.assertRaises(ValueError):
self.assertEqual(m[:], data[:new_size]) m.resize(start_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') @unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_resize_fails_if_mapping_held_elsewhere(self): def test_resize_fails_if_mapping_held_elsewhere(self):

View file

@ -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`.

View file

@ -628,6 +628,7 @@ is_writable(mmap_object *self)
return 0; return 0;
} }
#if defined(MS_WINDOWS) || defined(HAVE_MREMAP)
static int static int
is_resizeable(mmap_object *self) is_resizeable(mmap_object *self)
{ {
@ -648,6 +649,7 @@ is_resizeable(mmap_object *self)
return 0; return 0;
} }
#endif /* MS_WINDOWS || HAVE_MREMAP */
static PyObject * static PyObject *
@ -766,6 +768,7 @@ mmap_size_method(PyObject *op, PyObject *Py_UNUSED(ignored))
/ new size? / new size?
*/ */
#if defined(MS_WINDOWS) || defined(HAVE_MREMAP)
static PyObject * static PyObject *
mmap_resize_method(PyObject *op, PyObject *args) mmap_resize_method(PyObject *op, PyObject *args)
{ {
@ -880,11 +883,6 @@ mmap_resize_method(PyObject *op, PyObject *args)
#endif /* MS_WINDOWS */ #endif /* MS_WINDOWS */
#ifdef UNIX #ifdef UNIX
#ifndef HAVE_MREMAP
PyErr_SetString(PyExc_SystemError,
"mmap: resizing not available--no mremap()");
return NULL;
#else
void *newmap; void *newmap;
#ifdef __linux__ #ifdef __linux__
@ -916,10 +914,10 @@ mmap_resize_method(PyObject *op, PyObject *args)
self->data = newmap; self->data = newmap;
self->size = new_size; self->size = new_size;
Py_RETURN_NONE; Py_RETURN_NONE;
#endif /* HAVE_MREMAP */
#endif /* UNIX */ #endif /* UNIX */
} }
} }
#endif /* MS_WINDOWS || HAVE_MREMAP */
static PyObject * static PyObject *
mmap_tell_method(PyObject *op, PyObject *Py_UNUSED(ignored)) 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", mmap_read_method, METH_VARARGS},
{"read_byte", mmap_read_byte_method, METH_NOARGS}, {"read_byte", mmap_read_byte_method, METH_NOARGS},
{"readline", mmap_read_line_method, METH_NOARGS}, {"readline", mmap_read_line_method, METH_NOARGS},
#if defined(MS_WINDOWS) || defined(HAVE_MREMAP)
{"resize", mmap_resize_method, METH_VARARGS}, {"resize", mmap_resize_method, METH_VARARGS},
#endif
{"seek", mmap_seek_method, METH_VARARGS}, {"seek", mmap_seek_method, METH_VARARGS},
{"seekable", mmap_seekable_method, METH_NOARGS}, {"seekable", mmap_seekable_method, METH_NOARGS},
{"size", mmap_size_method, METH_NOARGS}, {"size", mmap_size_method, METH_NOARGS},