Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Doc/library/mmap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------------
Expand Down
135 changes: 58 additions & 77 deletions Lib/test/test_mmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -911,32 +902,26 @@ 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
data = random.randbytes(start_size)

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
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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`.
12 changes: 6 additions & 6 deletions Modules/mmapmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -648,6 +649,7 @@ is_resizeable(mmap_object *self)
return 0;

}
#endif /* MS_WINDOWS || HAVE_MREMAP */


static PyObject *
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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__
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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},
Expand Down
Loading