From d42762cd6cf9c076b8e5f56c85f03ac61b03115c Mon Sep 17 00:00:00 2001 From: Zackery Spytz Date: Thu, 15 Apr 2021 10:43:16 -0600 Subject: [PATCH 01/14] bpo-34321: Add a trackfd parameter to mmap.mmap() If *trackfd* is False, the file descriptor specified by *fileno* will not be duplicated. --- Doc/library/mmap.rst | 9 ++++++++- Doc/whatsnew/3.10.rst | 7 +++++++ Lib/test/test_mmap.py | 10 ++++++++++ .../2021-04-15-10-41-51.bpo-34321.36m6_l.rst | 2 ++ Modules/mmapmodule.c | 15 ++++++++++----- 5 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-04-15-10-41-51.bpo-34321.36m6_l.rst diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index d9825b47c71333..42c3233952cc97 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -69,7 +69,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length .. audit-event:: mmap.__new__ fileno,length,access,offset mmap.mmap -.. class:: mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, access=ACCESS_DEFAULT[, offset]) +.. class:: mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, \ + access=ACCESS_DEFAULT[, offset], trackfd=True) :noindex: **(Unix version)** Maps *length* bytes from the file specified by the file @@ -100,10 +101,16 @@ To map anonymous memory, -1 should be passed as the fileno along with the length defaults to 0. *offset* must be a multiple of :const:`ALLOCATIONGRANULARITY` which is equal to :const:`PAGESIZE` on Unix systems. + If *trackfd* is ``False``, the file descriptor specified by *fileno* will + not be duplicated. + To ensure validity of the created memory mapping the file specified by the descriptor *fileno* is internally automatically synchronized with physical backing store on Mac OS X and OpenVMS. + .. versionchanged:: 3.10 + The *trackfd* parameter was added. + This example shows a simple way of using :class:`~mmap.mmap`:: import mmap diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 0962f04b3a7d9c..ca66434eddc6d5 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -831,6 +831,13 @@ linecache When a module does not define ``__loader__``, fall back to ``__spec__.loader``. (Contributed by Brett Cannon in :issue:`42133`.) +mmap +---- + +:class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is ``False``, +the file descriptor specified by *fileno* will not be duplicated. +(Contributed by Zackery Spytz in :issue:`34321`.) + os -- diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 8f34c182f82eaf..f4a39ffbbb6d55 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -246,6 +246,16 @@ def test_access_parameter(self): self.assertRaises(TypeError, m.write_byte, 0) m.close() + @unittest.skipIf(os.name == 'nt', 'trackfd not present on Windows') + def test_trackfd_parameter(self): + size = 64 + with open(TESTFN, "wb") as f: + f.write(b"a"*size) + with open(TESTFN, "r+b") as f: + m = mmap.mmap(f.fileno(), size, trackfd=False) + self.assertEqual(len(m), size) + m.close() + def test_bad_file_desc(self): # Try opening a bad file descriptor... self.assertRaises(OSError, mmap.mmap, -2, 4096) diff --git a/Misc/NEWS.d/next/Library/2021-04-15-10-41-51.bpo-34321.36m6_l.rst b/Misc/NEWS.d/next/Library/2021-04-15-10-41-51.bpo-34321.36m6_l.rst new file mode 100644 index 00000000000000..85912942eedf5a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-04-15-10-41-51.bpo-34321.36m6_l.rst @@ -0,0 +1,2 @@ +:class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is +``False``, the file descriptor specified by *fileno* will not be duplicated. diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 1e66962d37b0e0..5e2a8c77c85d25 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -1136,15 +1136,17 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) off_t offset = 0; int fd, flags = MAP_SHARED, prot = PROT_WRITE | PROT_READ; int devzero = -1; - int access = (int)ACCESS_DEFAULT; + int access = (int)ACCESS_DEFAULT, trackfd = 1; static char *keywords[] = {"fileno", "length", "flags", "prot", - "access", "offset", NULL}; + "access", "offset", "trackfd", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwdict, "in|iii" _Py_PARSE_OFF_T, keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwdict, + "in|iii" _Py_PARSE_OFF_T "p", keywords, &fd, &map_size, &flags, &prot, - &access, &offset)) + &access, &offset, &trackfd)) { return NULL; + } if (map_size < 0) { PyErr_SetString(PyExc_OverflowError, "memory mapped length must be positive"); @@ -1265,13 +1267,16 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) } #endif } - else { + else if (trackfd) { m_obj->fd = _Py_dup(fd); if (m_obj->fd == -1) { Py_DECREF(m_obj); return NULL; } } + else { + m_obj->fd = -1; + } m_obj->data = mmap(NULL, map_size, prot, flags, From 42302bcd5fbbdb8f8f95c29aee207002e9c7206c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 11:13:50 +0100 Subject: [PATCH 02/14] Make the trackfd argument keyword-only --- Modules/mmapmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 1e9325e2713270..93203fe76ddd04 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -1231,7 +1231,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) "access", "offset", "trackfd", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwdict, - "in|iii" _Py_PARSE_OFF_T "p", keywords, + "in|iii" _Py_PARSE_OFF_T "$p", keywords, &fd, &map_size, &flags, &prot, &access, &offset, &trackfd)) { return NULL; From 9443f1fb184b467cbd32eeb72516fe000b0049a0 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 11:58:51 +0100 Subject: [PATCH 03/14] Add smoke tests for a mmap with trackfd=False --- Lib/test/test_mmap.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 662f931dd654eb..2aa04ed6ee9d18 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -272,9 +272,28 @@ def test_trackfd_parameter(self): with open(TESTFN, "wb") as f: f.write(b"a"*size) with open(TESTFN, "r+b") as f: - m = mmap.mmap(f.fileno(), size, trackfd=False) - self.assertEqual(len(m), size) - m.close() + with mmap.mmap(f.fileno(), size, trackfd=False) as m: + self.assertEqual(len(m), size) + with self.assertRaises(OSError): + m.size() + self.assertEqual(m.closed, False) + + # Smoke-test other API + m.resize(size * 2) + m.write_byte(ord('X')) + m[2] = ord('Y') + m[size + 1] = ord('Z') + m.flush() + with open(TESTFN, "rb") as f: + self.assertEqual(f.read(4), b'XaYa') + self.assertEqual(m.tell(), 1) + m.seek(0) + self.assertEqual(m.tell(), 0) + self.assertEqual(m.read_byte(), ord('X')) + self.assertEqual(m[size + 1], 0) # TODO: resize doesn't quite work + + self.assertEqual(m.closed, True) + self.assertEqual(os.stat(TESTFN).st_size, size) def test_bad_file_desc(self): # Try opening a bad file descriptor... From eb8d79825d0d4ff076fc7d1f1d69f6da492d3e62 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 12:48:01 +0100 Subject: [PATCH 04/14] Update documentation --- Doc/library/mmap.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index caf2fa1f9264c7..174e7d09c8520d 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -48,7 +48,7 @@ update the underlying file. To map anonymous memory, -1 should be passed as the fileno along with the length. -.. class:: mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT[, offset]) +.. class:: mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT, offset=0) **(Windows version)** Maps *length* bytes from the file specified by the file handle *fileno*, and creates a mmap object. If *length* is larger @@ -72,7 +72,7 @@ To map anonymous memory, -1 should be passed as the fileno along with the length .. audit-event:: mmap.__new__ fileno,length,access,offset mmap.mmap .. class:: mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, \ - access=ACCESS_DEFAULT[, offset], trackfd=True) + access=ACCESS_DEFAULT, offset=0, trackfd=True) :noindex: **(Unix version)** Maps *length* bytes from the file specified by the file @@ -104,13 +104,16 @@ To map anonymous memory, -1 should be passed as the fileno along with the length which is equal to :const:`PAGESIZE` on Unix systems. If *trackfd* is ``False``, the file descriptor specified by *fileno* will - not be duplicated. + not be duplicated, and the resulting :class:`~!mmap.mmap` object will not + be associated with the map's underlying file. + This means that :meth:`~mmap.mmap.size` method will fail, and the + :meth:`~mmap.mmap.resize` method will not resize the underlying file. To ensure validity of the created memory mapping the file specified by the descriptor *fileno* is internally automatically synchronized with the physical backing store on macOS. - .. versionchanged:: 3.10 + .. versionchanged:: 3.13 The *trackfd* parameter was added. This example shows a simple way of using :class:`~mmap.mmap`:: @@ -270,6 +273,10 @@ 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. + **On Unix**. If the ``mmap`` object was created with *trackfd=False*, + this method only resizes the map, but not the underlying file. + This may leave part of the memory unmapped. + .. versionchanged:: 3.11 Correctly fails if attempting to resize when another map is held Allows resize against an anonymous map on Windows From 8f05c7b699cfcc345554faa003e5591e2ed736b6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 13:22:59 +0100 Subject: [PATCH 05/14] Make resize() fail with trackfd=False --- Doc/library/mmap.rst | 11 ++++------- Lib/test/test_mmap.py | 7 ++++--- Modules/mmapmodule.c | 9 +++++++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index 174e7d09c8520d..d7c13416f93574 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -106,8 +106,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length If *trackfd* is ``False``, the file descriptor specified by *fileno* will not be duplicated, and the resulting :class:`~!mmap.mmap` object will not be associated with the map's underlying file. - This means that :meth:`~mmap.mmap.size` method will fail, and the - :meth:`~mmap.mmap.resize` method will not resize the underlying file. + This means that the :meth:`~mmap.mmap.size` and :meth:`~mmap.mmap.resize` + methods will fail. To ensure validity of the created memory mapping the file specified by the descriptor *fileno* is internally automatically synchronized @@ -265,7 +265,8 @@ To map anonymous memory, -1 should be passed as the fileno along with the length .. method:: resize(newsize) Resizes the map and the underlying file, if any. If the mmap was created - with :const:`ACCESS_READ` or :const:`ACCESS_COPY`, resizing the map will + with *access* of :const:`ACCESS_READ` or :const:`ACCESS_COPY`, + or with *trackfd* set to ``False``, resizing the map will raise a :exc:`TypeError` exception. **On Windows**: Resizing the map will raise an :exc:`OSError` if there are other @@ -273,10 +274,6 @@ 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. - **On Unix**. If the ``mmap`` object was created with *trackfd=False*, - this method only resizes the map, but not the underlying file. - This may leave part of the memory unmapped. - .. 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/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 2aa04ed6ee9d18..7d7c8060d00f36 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -276,13 +276,15 @@ def test_trackfd_parameter(self): self.assertEqual(len(m), size) with self.assertRaises(OSError): m.size() + with self.assertRaises(TypeError): + m.resize(size * 2) + with self.assertRaises(TypeError): + m.resize(size // 2) self.assertEqual(m.closed, False) # Smoke-test other API - m.resize(size * 2) m.write_byte(ord('X')) m[2] = ord('Y') - m[size + 1] = ord('Z') m.flush() with open(TESTFN, "rb") as f: self.assertEqual(f.read(4), b'XaYa') @@ -290,7 +292,6 @@ def test_trackfd_parameter(self): m.seek(0) self.assertEqual(m.tell(), 0) self.assertEqual(m.read_byte(), ord('X')) - self.assertEqual(m[size + 1], 0) # TODO: resize doesn't quite work self.assertEqual(m.closed, True) self.assertEqual(os.stat(TESTFN).st_size, size) diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index 93203fe76ddd04..feecbe13a5fc77 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -121,6 +121,7 @@ typedef struct { #ifdef UNIX int fd; + _Bool trackfd; #endif PyObject *weakreflist; @@ -397,6 +398,13 @@ is_resizeable(mmap_object *self) "mmap can't resize with extant buffers exported."); return 0; } +#ifdef UNIX + if (!self->trackfd) { + PyErr_SetString(PyExc_TypeError, + "mmap can't resize with trackfd=False."); + return 0; + } +#endif if ((self->access == ACCESS_WRITE) || (self->access == ACCESS_DEFAULT)) return 1; PyErr_Format(PyExc_TypeError, @@ -1331,6 +1339,7 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict) m_obj->weakreflist = NULL; m_obj->exports = 0; m_obj->offset = offset; + m_obj->trackfd = trackfd; if (fd == -1) { m_obj->fd = -1; /* Assume the caller wants to map anonymous memory. From b2075589d6d264de3ccc4bd1d731e2ec3dc29ec9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 15:56:04 +0100 Subject: [PATCH 06/14] Test with original fd closed after creating mmap --- Lib/test/test_mmap.py | 52 +++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 7d7c8060d00f36..b2de057e7a1cf5 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -271,30 +271,34 @@ def test_trackfd_parameter(self): size = 64 with open(TESTFN, "wb") as f: f.write(b"a"*size) - with open(TESTFN, "r+b") as f: - with mmap.mmap(f.fileno(), size, trackfd=False) as m: - self.assertEqual(len(m), size) - with self.assertRaises(OSError): - m.size() - with self.assertRaises(TypeError): - m.resize(size * 2) - with self.assertRaises(TypeError): - m.resize(size // 2) - self.assertEqual(m.closed, False) - - # Smoke-test other API - m.write_byte(ord('X')) - m[2] = ord('Y') - m.flush() - with open(TESTFN, "rb") as f: - self.assertEqual(f.read(4), b'XaYa') - self.assertEqual(m.tell(), 1) - m.seek(0) - self.assertEqual(m.tell(), 0) - self.assertEqual(m.read_byte(), ord('X')) - - self.assertEqual(m.closed, True) - self.assertEqual(os.stat(TESTFN).st_size, size) + for close_original_fd in True, False: + with self.subTest(close_original_fd=close_original_fd): + with open(TESTFN, "r+b") as f: + with mmap.mmap(f.fileno(), size, trackfd=False) as m: + if close_original_fd: + f.close() + self.assertEqual(len(m), size) + with self.assertRaises(OSError): + m.size() + with self.assertRaises(TypeError): + m.resize(size * 2) + with self.assertRaises(TypeError): + m.resize(size // 2) + self.assertEqual(m.closed, False) + + # Smoke-test other API + m.write_byte(ord('X')) + m[2] = ord('Y') + m.flush() + with open(TESTFN, "rb") as f: + self.assertEqual(f.read(4), b'XaYa') + self.assertEqual(m.tell(), 1) + m.seek(0) + self.assertEqual(m.tell(), 0) + self.assertEqual(m.read_byte(), ord('X')) + + self.assertEqual(m.closed, True) + self.assertEqual(os.stat(TESTFN).st_size, size) def test_bad_file_desc(self): # Try opening a bad file descriptor... From 76075938165779d8b8c6e44af9b51ed981c7f18b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 15:59:30 +0100 Subject: [PATCH 07/14] Document trackfd as keyword-only --- Doc/library/mmap.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index d7c13416f93574..b3ec75e28683ff 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -72,7 +72,7 @@ To map anonymous memory, -1 should be passed as the fileno along with the length .. audit-event:: mmap.__new__ fileno,length,access,offset mmap.mmap .. class:: mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, \ - access=ACCESS_DEFAULT, offset=0, trackfd=True) + access=ACCESS_DEFAULT, offset=0, *, trackfd=True) :noindex: **(Unix version)** Maps *length* bytes from the file specified by the file From 2d902299dd3726ea2cef1121b95fdf54de52f586 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 16:03:29 +0100 Subject: [PATCH 08/14] Update docstring --- Modules/mmapmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index feecbe13a5fc77..ea12e7b22242bb 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -1166,7 +1166,7 @@ is 0, the maximum length of the map is the current size of the file,\n\ except that if the file is empty Windows raises an exception (you cannot\n\ create an empty mapping on Windows).\n\ \n\ -Unix: mmap(fileno, length[, flags[, prot[, access[, offset]]]])\n\ +Unix: mmap(fileno, length[, flags[, prot[, access[, offset[, trackfd]]]]])\n\ \n\ Maps length bytes from the file specified by the file descriptor fileno,\n\ and returns a mmap object. If length is 0, the maximum length of the map\n\ From 0a717f8d6aad1a44dc9dd85c301e8a91c1e6418e Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 16:03:45 +0100 Subject: [PATCH 09/14] Test with fd=-1, and that this fails on Windows --- Lib/test/test_mmap.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index b2de057e7a1cf5..13fdaa1e1b7b70 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -300,6 +300,27 @@ def test_trackfd_parameter(self): self.assertEqual(m.closed, True) self.assertEqual(os.stat(TESTFN).st_size, size) + @unittest.skipIf(os.name == 'nt', 'trackfd not present on Windows') + def test_trackfd_neg1(self): + size = 64 + with mmap.mmap(-1, size, trackfd=False) as m: + with self.assertRaises(OSError): + m.size() + with self.assertRaises(TypeError): + m.resize(size // 2) + self.assertEqual(len(m), size) + m[0] = ord('a') + assert m[0] == ord('a') + + @unittest.skipIf(os.name != 'nt', 'trackfd only fails on Windows') + def test_no_trackfd_parameter_on_windows(self): + # 'trackffd' is an invalid keyword argument for this function + size = 64 + with self.assertRaises(TypeError): + mmap.mmap(-1, size, trackfd=True) + with self.assertRaises(TypeError): + mmap.mmap(-1, size, trackfd=False) + def test_bad_file_desc(self): # Try opening a bad file descriptor... self.assertRaises(OSError, mmap.mmap, -2, 4096) From 7364a27e9f26a7645e4e8d05011a9d38ac5d0aea Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 9 Jan 2024 16:09:59 +0100 Subject: [PATCH 10/14] Test the errno --- Lib/test/test_mmap.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 13fdaa1e1b7b70..d67717bdb083be 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -4,6 +4,7 @@ from test.support.import_helper import import_module from test.support.os_helper import TESTFN, unlink import unittest +import errno import os import re import itertools @@ -278,8 +279,9 @@ def test_trackfd_parameter(self): if close_original_fd: f.close() self.assertEqual(len(m), size) - with self.assertRaises(OSError): + with self.assertRaises(OSError) as err_cm: m.size() + self.assertEqual(err_cm.exception.errno, errno.EBADF) with self.assertRaises(TypeError): m.resize(size * 2) with self.assertRaises(TypeError): From eca802c987bcae684a01bee99e30440f5994c32c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 10 Jan 2024 14:09:08 +0100 Subject: [PATCH 11/14] Move the What's New entry to 3.13 --- Doc/whatsnew/3.10.rst | 7 ------- Doc/whatsnew/3.13.rst | 3 +++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 214c67944e3c8d..7dc06e9af694d9 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -1242,13 +1242,6 @@ linecache When a module does not define ``__loader__``, fall back to ``__spec__.loader``. (Contributed by Brett Cannon in :issue:`42133`.) -mmap ----- - -:class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is ``False``, -the file descriptor specified by *fileno* will not be duplicated. -(Contributed by Zackery Spytz in :issue:`34321`.) - os -- diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 59b9281e6d2b89..7a10edf86ae6bd 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -254,6 +254,9 @@ mmap that can be used where it requires a file-like object with seekable and the :meth:`~mmap.mmap.seek` method return the new absolute position. (Contributed by Donghee Na and Sylvie Liberman in :gh:`111835`.) +* :class:`mmap.mmap` now has a *trackfd* parameter on Unix; if it is ``False``, + the file descriptor specified by *fileno* will not be duplicated. + (Contributed by Zackery Spytz and Petr Viktorin in :gh:`78502`.) opcode ------ From 51bb4e5c6c65d03b9804a256d1ca20331b496067 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 10 Jan 2024 14:00:23 +0100 Subject: [PATCH 12/14] Raise ValueEror on resize --- Doc/library/mmap.rst | 10 ++++++---- Lib/test/test_mmap.py | 6 +++--- Modules/mmapmodule.c | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index b3ec75e28683ff..7b1a220eb70106 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -264,10 +264,12 @@ To map anonymous memory, -1 should be passed as the fileno along with the length .. method:: resize(newsize) - Resizes the map and the underlying file, if any. If the mmap was created - with *access* of :const:`ACCESS_READ` or :const:`ACCESS_COPY`, - or with *trackfd* set to ``False``, resizing the map will - raise a :exc:`TypeError` exception. + Resizes the map and the underlying file, if any. + + Resizing a map created with *access* of :const:`ACCESS_READ` or + :const:`ACCESS_COPY`, will raise a :exc:`TypeError` exception. + Resizing a map created with with *trackfd* set to ``False``, + will raise a :exc:`ValueError` exception. **On Windows**: Resizing the map will raise an :exc:`OSError` if there are other maps against the same named file. Resizing an anonymous map (ie against the diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index d67717bdb083be..b72cf3e73fd415 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -282,9 +282,9 @@ def test_trackfd_parameter(self): with self.assertRaises(OSError) as err_cm: m.size() self.assertEqual(err_cm.exception.errno, errno.EBADF) - with self.assertRaises(TypeError): + with self.assertRaises(ValueError): m.resize(size * 2) - with self.assertRaises(TypeError): + with self.assertRaises(ValueError): m.resize(size // 2) self.assertEqual(m.closed, False) @@ -308,7 +308,7 @@ def test_trackfd_neg1(self): with mmap.mmap(-1, size, trackfd=False) as m: with self.assertRaises(OSError): m.size() - with self.assertRaises(TypeError): + with self.assertRaises(ValueError): m.resize(size // 2) self.assertEqual(len(m), size) m[0] = ord('a') diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index ea12e7b22242bb..901f42cff88736 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -400,7 +400,7 @@ is_resizeable(mmap_object *self) } #ifdef UNIX if (!self->trackfd) { - PyErr_SetString(PyExc_TypeError, + PyErr_SetString(PyExc_ValueError, "mmap can't resize with trackfd=False."); return 0; } From d2749524e6cef3eada6ef25b5f7b293788131bfa Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 10 Jan 2024 17:26:05 +0100 Subject: [PATCH 13/14] Fix Sphinx warning Co-authored-by: Serhiy Storchaka --- Doc/library/mmap.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index 7b1a220eb70106..b8cd602e206108 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -104,7 +104,7 @@ To map anonymous memory, -1 should be passed as the fileno along with the length which is equal to :const:`PAGESIZE` on Unix systems. If *trackfd* is ``False``, the file descriptor specified by *fileno* will - not be duplicated, and the resulting :class:`~!mmap.mmap` object will not + not be duplicated, and the resulting :class:`!mmap` object will not be associated with the map's underlying file. This means that the :meth:`~mmap.mmap.size` and :meth:`~mmap.mmap.resize` methods will fail. From e33a7fd1314aac70f666a7e09f7a39644ca02757 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 15 Jan 2024 15:05:38 +0100 Subject: [PATCH 14/14] Note why you might want to use this --- Doc/library/mmap.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/mmap.rst b/Doc/library/mmap.rst index b8cd602e206108..1ded97df7fae90 100644 --- a/Doc/library/mmap.rst +++ b/Doc/library/mmap.rst @@ -108,6 +108,7 @@ To map anonymous memory, -1 should be passed as the fileno along with the length be associated with the map's underlying file. This means that the :meth:`~mmap.mmap.size` and :meth:`~mmap.mmap.resize` methods will fail. + This mode is useful to limit the number of open file descriptors. To ensure validity of the created memory mapping the file specified by the descriptor *fileno* is internally automatically synchronized