]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142419: Add mmap.set_name method for user custom annotation (gh-142480)
authorDonghee Na <donghee.na@python.org>
Thu, 18 Dec 2025 14:33:49 +0000 (23:33 +0900)
committerGitHub <noreply@github.com>
Thu, 18 Dec 2025 14:33:49 +0000 (23:33 +0900)
Doc/library/mmap.rst
Doc/whatsnew/3.15.rst
Include/internal/pycore_mmap.h
Lib/test/test_mmap.py
Misc/NEWS.d/next/Library/2025-12-10-02-31-43.gh-issue-142419.C8_LES.rst [new file with mode: 0644]
Modules/clinic/mmapmodule.c.h
Modules/mmapmodule.c
Objects/obmalloc.c
Python/jit.c
Python/perf_jit_trampoline.c
Python/perf_trampoline.c

index f32aa322c40dbb3e09e66f2e6a600250e4384a5a..41b90f2c3b3111a8d411c19afb4ccc2aab6699ad 100644 (file)
@@ -328,6 +328,17 @@ To map anonymous memory, -1 should be passed as the fileno along with the length
 
       .. versionadded:: 3.13
 
+   .. method:: set_name(name, /)
+
+      Annotate the memory mapping with the given *name* for easier identification
+      in ``/proc/<pid>/maps`` if the kernel supports the feature and :option:`-X dev <-X>` is passed
+      to Python or if Python is built in :ref:`debug mode <debug-build>`.
+      The length of *name* must not exceed 67 bytes including the ``'\0'`` terminator.
+
+      .. availability:: Linux >= 5.17 (kernel built with ``CONFIG_ANON_VMA_NAME`` option)
+
+      .. versionadded:: next
+
    .. method:: size()
 
       Return the length of the file, which can be larger than the size of the
index 753b1990eb3308ae6ed064ffc9e4aa4ebb63976a..3c7e28a00c9c1a44b72cdc7901920e11986f5404 100644 (file)
@@ -592,6 +592,11 @@ mmap
   not be duplicated.
   (Contributed by Serhiy Storchaka in :gh:`78502`.)
 
+* Added the :meth:`mmap.mmap.set_name` method
+  to annotate an anonymous memory mapping
+  if Linux kernel supports :manpage:`PR_SET_VMA_ANON_NAME <PR_SET_VMA(2const)>` (Linux 5.17 or newer).
+  (Contributed by Donghee Na in :gh:`142419`.)
+
 
 os
 --
index 214fd4362a55fed787ba49b29b6ac044aa05f65b..897816db01077f6772998ea48d47ae8b9cebdaf0 100644 (file)
@@ -17,25 +17,27 @@ extern "C" {
 #endif
 
 #if defined(HAVE_PR_SET_VMA_ANON_NAME) && defined(__linux__)
-static inline void
+static inline int
 _PyAnnotateMemoryMap(void *addr, size_t size, const char *name)
 {
 #ifndef Py_DEBUG
     if (!_Py_GetConfig()->dev_mode) {
-        return;
+        return 0;
     }
 #endif
+    // The name length cannot exceed 80 (including the '\0').
     assert(strlen(name) < 80);
-    int old_errno = errno;
-    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
-    /* Ignore errno from prctl */
-    /* See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746 */
-    errno = old_errno;
+    int res = prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)addr, size, name);
+    if (res < 0) {
+        return -1;
+    }
+    return 0;
 }
 #else
-static inline void
+static inline int
 _PyAnnotateMemoryMap(void *Py_UNUSED(addr), size_t Py_UNUSED(size), const char *Py_UNUSED(name))
 {
+    return 0;
 }
 #endif
 
index 368af0cf89c3001a47923ce3b7112f69ed1dff1d..aad916ecfe2c27afb95b4c6805975f5f8c8767c2 100644 (file)
@@ -6,6 +6,7 @@ from test.support import (
 from test.support.import_helper import import_module
 from test.support.os_helper import TESTFN, unlink
 from test.support.script_helper import assert_python_ok
+import errno
 import unittest
 import os
 import re
@@ -1165,6 +1166,46 @@ class MmapTests(unittest.TestCase):
             m.flush(PAGESIZE)
             m.flush(PAGESIZE, PAGESIZE)
 
+    @unittest.skipUnless(sys.platform == 'linux', 'Linux only')
+    @support.requires_linux_version(5, 17, 0)
+    def test_set_name(self):
+        # Test setting name on anonymous mmap
+        m = mmap.mmap(-1, PAGESIZE)
+        self.addCleanup(m.close)
+        try:
+            result = m.set_name('test_mapping')
+        except OSError as exc:
+            if exc.errno == errno.EINVAL:
+                # gh-142419: On Fedora, prctl(PR_SET_VMA_ANON_NAME) fails with
+                # EINVAL because the kernel option CONFIG_ANON_VMA_NAME is
+                # disabled.
+                # See: https://bugzilla.redhat.com/show_bug.cgi?id=2302746
+                self.skipTest("prctl() failed with EINVAL")
+            else:
+                raise
+        self.assertIsNone(result)
+
+        # Test name length limit (80 chars including prefix "cpython:mmap:" and '\0')
+        # Prefix is 13 chars, so max name is 66 chars
+        long_name = 'x' * 66
+        result = m.set_name(long_name)
+        self.assertIsNone(result)
+
+        # Test name too long
+        too_long_name = 'x' * 67
+        with self.assertRaises(ValueError):
+            m.set_name(too_long_name)
+
+        # Test that file-backed mmap raises error
+        with open(TESTFN, 'wb+') as f:
+            f.write(b'x' * PAGESIZE)
+            f.flush()
+            m2 = mmap.mmap(f.fileno(), PAGESIZE)
+            self.addCleanup(m2.close)
+
+            with self.assertRaises(ValueError):
+                m2.set_name('should_fail')
+
 
 class LargeMmapTests(unittest.TestCase):
 
diff --git a/Misc/NEWS.d/next/Library/2025-12-10-02-31-43.gh-issue-142419.C8_LES.rst b/Misc/NEWS.d/next/Library/2025-12-10-02-31-43.gh-issue-142419.C8_LES.rst
new file mode 100644 (file)
index 0000000..6395592
--- /dev/null
@@ -0,0 +1,3 @@
+:meth:`mmap.mmap.set_name` method added to annotate an anonymous memory map
+if Linux kernel supports ``PR_SET_VMA_ANON_NAME`` (Linux 5.17 or newer).
+Patch by Donghee Na.
index f7fc172b3af705f8785c44bb610506a2c087c86f..b63f7df2a7e33480361e2264a23146b6ca919264 100644 (file)
@@ -479,6 +479,42 @@ exit:
     return return_value;
 }
 
+PyDoc_STRVAR(mmap_mmap_set_name__doc__,
+"set_name($self, name, /)\n"
+"--\n"
+"\n");
+
+#define MMAP_MMAP_SET_NAME_METHODDEF    \
+    {"set_name", (PyCFunction)mmap_mmap_set_name, METH_O, mmap_mmap_set_name__doc__},
+
+static PyObject *
+mmap_mmap_set_name_impl(mmap_object *self, const char *name);
+
+static PyObject *
+mmap_mmap_set_name(PyObject *self, PyObject *arg)
+{
+    PyObject *return_value = NULL;
+    const char *name;
+
+    if (!PyUnicode_Check(arg)) {
+        _PyArg_BadArgument("set_name", "argument", "str", arg);
+        goto exit;
+    }
+    Py_ssize_t name_length;
+    name = PyUnicode_AsUTF8AndSize(arg, &name_length);
+    if (name == NULL) {
+        goto exit;
+    }
+    if (strlen(name) != (size_t)name_length) {
+        PyErr_SetString(PyExc_ValueError, "embedded null character");
+        goto exit;
+    }
+    return_value = mmap_mmap_set_name_impl((mmap_object *)self, name);
+
+exit:
+    return return_value;
+}
+
 PyDoc_STRVAR(mmap_mmap_seekable__doc__,
 "seekable($self, /)\n"
 "--\n"
@@ -796,4 +832,4 @@ exit:
 #ifndef MMAP_MMAP_MADVISE_METHODDEF
     #define MMAP_MMAP_MADVISE_METHODDEF
 #endif /* !defined(MMAP_MMAP_MADVISE_METHODDEF) */
-/*[clinic end generated code: output=381f6cf4986ac867 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=fd9ca0ef425af934 input=a9049054013a1b77]*/
index 37003020de26889824f391eb1cbf546978b18a09..ea20fb29c902285d49a89ba19a54763e6ce14026 100644 (file)
@@ -1117,6 +1117,47 @@ mmap_mmap_seek_impl(mmap_object *self, Py_ssize_t dist, int how)
     return NULL;
 }
 
+/*[clinic input]
+mmap.mmap.set_name
+
+    name: str
+    /
+
+[clinic start generated code]*/
+
+static PyObject *
+mmap_mmap_set_name_impl(mmap_object *self, const char *name)
+/*[clinic end generated code: output=1edaf4fd51277760 input=6c7dd91cad205f07]*/
+{
+#if defined(MAP_ANONYMOUS) && defined(__linux__)
+    const char *prefix = "cpython:mmap:";
+    if (strlen(name) + strlen(prefix) > 79) {
+        PyErr_SetString(PyExc_ValueError, "name is too long");
+        return NULL;
+    }
+    if (self->flags & MAP_ANONYMOUS) {
+        char buf[80];
+        sprintf(buf, "%s%s", prefix, name);
+        if (_PyAnnotateMemoryMap(self->data, self->size, buf) < 0) {
+            PyErr_SetFromErrno(PyExc_OSError);
+            return NULL;
+        }
+        Py_RETURN_NONE;
+    }
+    else {
+        /* cannot name non-anonymous mappings */
+        PyErr_SetString(PyExc_ValueError,
+                        "Cannot set annotation on non-anonymous mappings");
+        return NULL;
+    }
+#else
+    /* naming not supported on this platform */
+    PyErr_SetString(PyExc_NotImplementedError,
+                    "Annotation of mmap is not supported on this platform");
+    return NULL;
+#endif
+}
+
 /*[clinic input]
 mmap.mmap.seekable
 
@@ -1397,6 +1438,7 @@ static struct PyMethodDef mmap_object_methods[] = {
     MMAP_MMAP_RESIZE_METHODDEF
     MMAP_MMAP_SEEK_METHODDEF
     MMAP_MMAP_SEEKABLE_METHODDEF
+    MMAP_MMAP_SET_NAME_METHODDEF
     MMAP_MMAP_SIZE_METHODDEF
     MMAP_MMAP_TELL_METHODDEF
     MMAP_MMAP_WRITE_METHODDEF
@@ -1952,7 +1994,11 @@ new_mmap_object(PyTypeObject *type, PyObject *args, PyObject *kwdict)
         PyErr_SetFromErrno(PyExc_OSError);
         return NULL;
     }
-    _PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
+#ifdef MAP_ANONYMOUS
+    if (m_obj->flags & MAP_ANONYMOUS) {
+        (void)_PyAnnotateMemoryMap(m_obj->data, map_size, "cpython:mmap");
+    }
+#endif
     m_obj->access = (access_mode)access;
     return (PyObject *)m_obj;
 }
index b1f9fa2e692265c0eb3580dd51489496100fb4d9..c4ccc9e283feb3e5434fbea5ab1922240f7960aa 100644 (file)
@@ -468,7 +468,7 @@ _PyMem_ArenaAlloc(void *Py_UNUSED(ctx), size_t size)
     if (ptr == MAP_FAILED)
         return NULL;
     assert(ptr != NULL);
-    _PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
+    (void)_PyAnnotateMemoryMap(ptr, size, "cpython:pymalloc");
     return ptr;
 #else
     return malloc(size);
index ccafe0ce497f430568a214bfaf4dd87a653c7d3d..7660f6f9beac890642994fbd7b81dde29b42b564 100644 (file)
@@ -77,7 +77,7 @@ jit_alloc(size_t size)
     unsigned char *memory = mmap(NULL, size, prot, flags, -1, 0);
     int failed = memory == MAP_FAILED;
     if (!failed) {
-        _PyAnnotateMemoryMap(memory, size, "cpython:jit");
+        (void)_PyAnnotateMemoryMap(memory, size, "cpython:jit");
     }
 #endif
     if (failed) {
index af7d8f9f1ec0ae254c41c6339ba70ce6b8d478af..0ffa906d85cc6bc5de526236a2c59802a6c90c32 100644 (file)
@@ -1086,7 +1086,8 @@ static void* perf_map_jit_init(void) {
         close(fd);
         return NULL;  // Memory mapping failed
     }
-    _PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size, "cpython:perf_jit_trampoline");
+    (void)_PyAnnotateMemoryMap(perf_jit_map_state.mapped_buffer, page_size,
+                               "cpython:perf_jit_trampoline");
 #endif
 
     perf_jit_map_state.mapped_size = page_size;
index 669a47ae17377a40683ffb3b92ce8df50727c1dc..335d8ac7dadd1037902b1673c793261ad7040b99 100644 (file)
@@ -291,7 +291,7 @@ new_code_arena(void)
         perf_status = PERF_STATUS_FAILED;
         return -1;
     }
-    _PyAnnotateMemoryMap(memory, mem_size, "cpython:perf_trampoline");
+    (void)_PyAnnotateMemoryMap(memory, mem_size, "cpython:perf_trampoline");
     void *start = &_Py_trampoline_func_start;
     void *end = &_Py_trampoline_func_end;
     size_t code_size = end - start;