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
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

View file

@ -701,6 +701,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
-----------------

View file

@ -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)
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)
# 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.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):

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;
}
#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},