diff --git a/Doc/library/multiprocessing.shared_memory.rst b/Doc/library/multiprocessing.shared_memory.rst index e8f04a6ac7b95d..334d515503653a 100644 --- a/Doc/library/multiprocessing.shared_memory.rst +++ b/Doc/library/multiprocessing.shared_memory.rst @@ -112,6 +112,32 @@ copying of data. This method has no effect on Windows, where the only way to delete a shared memory block is to close all handles. + .. method:: rename(newname, flags=0) + + Rename the underlying shared memory block to *newname*. + By default, if *newname* already exists, it will be unlinked beforehand. + + *flags* (0 by default) takes :ref:`bitwise ORed ` flags together. + + .. availability:: FreeBSD >= 13.0 + + .. versionadded:: next + + .. data:: SHM_RENAME_EXCHANGE + SHM_RENAME_NOREPLACE + + Parameters to the :func:`rename` function. + + :const:`SHM_RENAME_EXCHANGE` + Atomically exchange the SharedMemory to the *newname*. + + :const:`SHM_RENAME_NOREPLACE` + Raise an error if *newname* exists, rather than unlinking it. + + .. availability:: FreeBSD >= 13.0 + + .. versionadded:: next + .. attribute:: buf A memoryview of contents of the shared memory block. diff --git a/Lib/multiprocessing/shared_memory.py b/Lib/multiprocessing/shared_memory.py index 99a8ce3320ad4e..690e27aaeeef0d 100644 --- a/Lib/multiprocessing/shared_memory.py +++ b/Lib/multiprocessing/shared_memory.py @@ -11,6 +11,7 @@ from functools import partial import mmap import os +import platform import errno import struct import secrets @@ -36,6 +37,10 @@ else: _SHM_NAME_PREFIX = 'wnsm_' +if _USE_POSIX and hasattr(_posixshmem, "shm_rename"): + from _posixshmem import SHM_RENAME_EXCHANGE + from _posixshmem import SHM_RENAME_NOREPLACE + def _make_filename(): "Create a random filename for the shared memory object." @@ -253,6 +258,27 @@ def unlink(self): if self._track: resource_tracker.unregister(self._name, "shared_memory") + if _USE_POSIX and hasattr(_posixshmem, "shm_rename"): + def rename(self, newname, flags=0): + """Renames a shared memory block. + + The policy how the operation is handled depends on the flag passed. + The default behavior is if the newname already exists, it will + be unlinked beforehand. + With the SHM_RENAME_EXCHANGE flag, the old and new name will + be exchanged. + With the SHM_RENAME_NOREPLACE flag, an error will be returned + if the new name exists. + """ + oldname = self._name + if _USE_POSIX and self._prepend_leading_slash: + newname = "/" + newname + self._fd = _posixshmem.shm_rename(self._name, newname, flags) + if self._track: + resource_tracker.unregister(oldname, "shared_memory") + resource_tracker.register(newname, "shared_memory") + self._name = newname + _encoding = "utf8" diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index dcce57629efe5b..fd39dd9614812e 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -4376,6 +4376,41 @@ def test_shared_memory_recreate(self): self.addCleanup(shm2.unlink) self.assertEqual(shm2._name, names[1]) + @unittest.skipUnless(hasattr(shared_memory.SharedMemory, "rename"), + "requires SharedMomery.rename") + def test_shared_memory_rename(self): + name1 = self._new_shm_name('testrename01_tsmb') + name2 = self._new_shm_name('testrename02_tsmb') + sms = shared_memory.SharedMemory(name1, create=True, size=512) + self.addCleanup(sms.unlink) + + sms.rename(name2) + self.assertEqual(sms.name, name2) + + @unittest.skipUnless(hasattr(shared_memory.SharedMemory, "rename"), + "requires SharedMomery.rename") + def test_shared_memory_rename_noreplace(self): + name1 = self._new_shm_name('testrename01_tsmb') + name2 = self._new_shm_name('testrename02_tsmb') + sms1 = shared_memory.SharedMemory(name1, create=True, size=512) + sms2 = shared_memory.SharedMemory(name2, create=True, size=512) + self.addCleanup(sms1.unlink) + self.addCleanup(sms2.unlink) + + with self.assertRaises(FileExistsError): + sms1.rename(name2, flags=shared_memory.SHM_RENAME_NOREPLACE) + + @unittest.skipUnless(hasattr(shared_memory.SharedMemory, "rename"), + "requires SharedMomery.rename") + def test_shared_memory_rename_exchange(self): + name1 = self._new_shm_name('testrename01_tsmb') + name2 = self._new_shm_name('testrename02_tsmb') + sms1 = shared_memory.SharedMemory(name1, create=True, size=512) + sms2 = shared_memory.SharedMemory(name2, create=True, size=512) + self.addCleanup(sms2.unlink) + + sms1.rename(name2, flags=shared_memory.SHM_RENAME_EXCHANGE) + def test_invalid_shared_memory_creation(self): # Test creating a shared memory segment with negative size with self.assertRaises(ValueError): diff --git a/Misc/NEWS.d/next/Library/2022-01-17-20-03-56.bpo-46398.FATeqM.rst b/Misc/NEWS.d/next/Library/2022-01-17-20-03-56.bpo-46398.FATeqM.rst new file mode 100644 index 00000000000000..d253aaf8ab35d8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-17-20-03-56.bpo-46398.FATeqM.rst @@ -0,0 +1 @@ +Add the :mod:`multiprocessing.shared_memory.SharedMemory.rename` method for FreeBSD. diff --git a/Modules/_multiprocessing/clinic/posixshmem.c.h b/Modules/_multiprocessing/clinic/posixshmem.c.h index a545ff4d80f067..e9109a3c2b8ba8 100644 --- a/Modules/_multiprocessing/clinic/posixshmem.c.h +++ b/Modules/_multiprocessing/clinic/posixshmem.c.h @@ -42,6 +42,67 @@ _posixshmem_shm_open(PyObject *module, PyObject *args, PyObject *kwargs) #endif /* defined(HAVE_SHM_OPEN) */ +#if defined(HAVE_SHM_RENAME) + +PyDoc_STRVAR(_posixshmem_shm_rename__doc__, +"shm_rename($module, path_from, path_to, flags, /)\n" +"--\n" +"\n" +"Rename a shared memory object.\n" +"\n" +"Remove a shared memory object and relink to another path.\n" +"By default, if the destination path already exist, it will be unlinked.\n" +"With the SHM_RENAME_EXCHANGE flag, source and destination paths\n" +"will be exchanged.\n" +"With the SHM_RENAME_NOREPLACE flag, an error will be triggered\n" +"if the destination alredady exists."); + +#define _POSIXSHMEM_SHM_RENAME_METHODDEF \ + {"shm_rename", (PyCFunction)(void(*)(void))_posixshmem_shm_rename, METH_FASTCALL, _posixshmem_shm_rename__doc__}, + +static int +_posixshmem_shm_rename_impl(PyObject *module, PyObject *path_from, + PyObject *path_to, int flags); + +static PyObject * +_posixshmem_shm_rename(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *path_from; + PyObject *path_to; + int flags; + int _return_value; + + if (nargs != 3) { + PyErr_Format(PyExc_TypeError, "shm_rename expected 3 arguments, got %zd", nargs); + goto exit; + } + if (!PyUnicode_Check(args[0])) { + PyErr_Format(PyExc_TypeError, "shm_rename() argument 1 must be str, not %T", args[0]); + goto exit; + } + path_from = args[0]; + if (!PyUnicode_Check(args[1])) { + PyErr_Format(PyExc_TypeError, "shm_rename() argument 2 must be str, not %T", args[1]); + goto exit; + } + path_to = args[1]; + flags = PyLong_AsInt(args[2]); + if (flags == -1 && PyErr_Occurred()) { + goto exit; + } + _return_value = _posixshmem_shm_rename_impl(module, path_from, path_to, flags); + if ((_return_value == -1) && PyErr_Occurred()) { + goto exit; + } + return_value = PyLong_FromLong((long)_return_value); + +exit: + return return_value; +} + +#endif /* defined(HAVE_SHM_RENAME) */ + #if defined(HAVE_SHM_UNLINK) PyDoc_STRVAR(_posixshmem_shm_unlink__doc__, @@ -83,7 +144,11 @@ _posixshmem_shm_unlink(PyObject *module, PyObject *arg) #define _POSIXSHMEM_SHM_OPEN_METHODDEF #endif /* !defined(_POSIXSHMEM_SHM_OPEN_METHODDEF) */ +#ifndef _POSIXSHMEM_SHM_RENAME_METHODDEF + #define _POSIXSHMEM_SHM_RENAME_METHODDEF +#endif /* !defined(_POSIXSHMEM_SHM_RENAME_METHODDEF) */ + #ifndef _POSIXSHMEM_SHM_UNLINK_METHODDEF #define _POSIXSHMEM_SHM_UNLINK_METHODDEF #endif /* !defined(_POSIXSHMEM_SHM_UNLINK_METHODDEF) */ -/*[clinic end generated code: output=74588a5abba6e36c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3b3a888f3ea27db0 input=a9049054013a1b77]*/ diff --git a/Modules/_multiprocessing/posixshmem.c b/Modules/_multiprocessing/posixshmem.c index ab45e4136c7d46..55e762cd58683c 100644 --- a/Modules/_multiprocessing/posixshmem.c +++ b/Modules/_multiprocessing/posixshmem.c @@ -73,6 +73,59 @@ _posixshmem_shm_open_impl(PyObject *module, PyObject *path, int flags, } #endif /* HAVE_SHM_OPEN */ +#ifdef HAVE_SHM_RENAME +/*[clinic input] +_posixshmem.shm_rename -> int + path_from: unicode + path_to: unicode + flags: int + / + +Rename a shared memory object. + +Remove a shared memory object and relink to another path. +By default, if the destination path already exist, it will be unlinked. +With the SHM_RENAME_EXCHANGE flag, source and destination paths +will be exchanged. +With the SHM_RENAME_NOREPLACE flag, an error will be triggered +if the destination alredady exists. + +[clinic start generated code]*/ + +static int +_posixshmem_shm_rename_impl(PyObject *module, PyObject *path_from, + PyObject *path_to, int flags) +/*[clinic end generated code: output=d9a710c512166e18 input=5fb42d1ce077caec]*/ +{ + int rv; + int async_err = 0; + Py_ssize_t from_size; + Py_ssize_t to_size; + const char *from = PyUnicode_AsUTF8AndSize(path_from, &from_size); + const char *to = PyUnicode_AsUTF8AndSize(path_to, &to_size); + if (from == NULL || to == NULL) { + return -1; + } + if (strlen(from) != (size_t)from_size || strlen(to) != (size_t)to_size) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return -1; + } + do { + Py_BEGIN_ALLOW_THREADS + rv = shm_rename(from, to, flags); + Py_END_ALLOW_THREADS + } while (rv < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); + + if (rv < 0) { + if (!async_err) + PyErr_SetFromErrnoWithFilenameObjects(PyExc_OSError, path_from, path_to); + return -1; + } + + return rv; +} +#endif /* HAVE_SHM_RENAME */ + #ifdef HAVE_SHM_UNLINK /*[clinic input] _posixshmem.shm_unlink @@ -122,12 +175,29 @@ _posixshmem_shm_unlink_impl(PyObject *module, PyObject *path) static PyMethodDef module_methods[ ] = { _POSIXSHMEM_SHM_OPEN_METHODDEF +#if defined(HAVE_SHM_RENAME) + _POSIXSHMEM_SHM_RENAME_METHODDEF +#endif _POSIXSHMEM_SHM_UNLINK_METHODDEF {NULL} /* Sentinel */ }; +static int +posixshmem_exec(PyObject *m) +{ +#ifdef HAVE_SHM_RENAME +#ifdef SHM_RENAME_EXCHANGE + if (PyModule_AddIntMacro(m, SHM_RENAME_EXCHANGE)) return -1; +#endif +#ifdef SHM_RENAME_NOREPLACE + if (PyModule_AddIntMacro(m, SHM_RENAME_NOREPLACE)) return -1; +#endif +#endif /* HAVE_SHM_RENAME */ + return 0; +} static PyModuleDef_Slot module_slots[] = { + {Py_mod_exec, posixshmem_exec}, {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0, NULL} diff --git a/configure b/configure index 1b75ddfa26dcdd..06b24bd594d3f4 100755 --- a/configure +++ b/configure @@ -29813,7 +29813,7 @@ fi #endif " - for ac_func in shm_open shm_unlink + for ac_func in shm_open shm_rename shm_unlink do : as_ac_var=`printf "%s\n" "ac_cv_func_$ac_func" | sed "$as_sed_sh"` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" diff --git a/configure.ac b/configure.ac index c449bb5ebb3cd4..4c77b93cdd48d6 100644 --- a/configure.ac +++ b/configure.ac @@ -7297,7 +7297,7 @@ WITH_SAVE_ENV([ # endif #endif " - AC_CHECK_FUNCS([shm_open shm_unlink], [have_posix_shmem=yes], [have_posix_shmem=no]) + AC_CHECK_FUNCS([shm_open shm_rename shm_unlink], [have_posix_shmem=yes], [have_posix_shmem=no]) _RESTORE_VAR([ac_includes_default]) ]) diff --git a/pyconfig.h.in b/pyconfig.h.in index aa086d49e90a5b..a7fe8b59511591 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1191,6 +1191,9 @@ /* Define to 1 if you have the 'shm_open' function. */ #undef HAVE_SHM_OPEN +/* Define to 1 if you have the 'shm_rename' function. */ +#undef HAVE_SHM_RENAME + /* Define to 1 if you have the 'shm_unlink' function. */ #undef HAVE_SHM_UNLINK