Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork33.7k
gh-82300: Add track parameter to shared memory#110778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
3be08b4f50812acbc843171384476bbfca6db79426db11894d44cb82c62cff0dcda10f63d21d7e990e419c593ba9ef1ff3e5fe67466acf903fdf625d65e3f8a97c6d317c07f513f3fb67c7f0e7765afb7a5848c18255e015d8911766db5b852053f8File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -36,7 +36,7 @@ or other communications requiring the serialization/deserialization and | ||
| copying of data. | ||
| .. class:: SharedMemory(name=None, create=False, size=0, *, track=True) | ||
| Creates a new shared memory block or attaches to an existing shared | ||
| memory block. Each shared memory block is assigned a unique name. | ||
| @@ -64,26 +64,45 @@ copying of data. | ||
| memory block may be larger or equal to the size requested. When attaching | ||
| to an existing shared memory block, the ``size`` parameter is ignored. | ||
| *track*, when enabled, registers the shared memory block with a resource | ||
| tracker process on platforms where the OS does not do this automatically. | ||
| The resource tracker ensures proper cleanup of the shared memory even | ||
| if all other processes with access to the memory exit without doing so. | ||
| Python processes created from a common ancestor using :mod:`multiprocessing` | ||
| facilities share a single resource tracker process, and the lifetime of | ||
| shared memory segments is handled automatically among these processes. | ||
| Python processes created in any other way will receive their own | ||
| resource tracker when accessing shared memory with *track* enabled. | ||
| This will cause the shared memory to be deleted by the resource tracker | ||
| of the first process that terminates. | ||
| To avoid this issue, users of :mod:`subprocess` or standalone Python | ||
| processes should set *track* to ``False`` when there is already another | ||
| process in place that does the bookkeeping. | ||
| *track* is ignored on Windows, which has its own tracking and | ||
| automatically deletes shared memory when all handles to it have been closed. | ||
| .. versionchanged:: 3.13 Added *track* parameter. | ||
| .. method:: close() | ||
| Closes the file descriptor/handle to the shared memory from this | ||
| instance. :meth:`close()` should be called once access to the shared | ||
| memory block from this instance is no longer needed. Depending | ||
| on operating system, the underlying memory may or may not be freed | ||
| even if all handles to it have been closed. To ensure proper cleanup, | ||
| use the :meth:`unlink()` method. | ||
| .. method:: unlink() | ||
| Deletes the underlying shared memory block. This should be called only | ||
gpshead marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| once per shared memory block regardless of the number of handles to it, | ||
| even in other processes. | ||
| :meth:`unlink()` and :meth:`close()` can be called in any order, but | ||
| trying to access data inside a shared memory block after :meth:`unlink()` | ||
| may result in memory access errors, depending on platform. | ||
pan324 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| This method has no effect on Windows, where the only way to delete a | ||
| shared memory block is to close all handles. | ||
| .. attribute:: buf | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -71,8 +71,9 @@ class SharedMemory: | ||
| _flags = os.O_RDWR | ||
| _mode = 0o600 | ||
| _prepend_leading_slash = True if _USE_POSIX else False | ||
| _track = True | ||
pan324 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| def __init__(self, name=None, create=False, size=0, *, track=True): | ||
| if not size >= 0: | ||
| raise ValueError("'size' must be a positive integer") | ||
| if create: | ||
| @@ -82,6 +83,7 @@ def __init__(self, name=None, create=False, size=0): | ||
| if name is None and not self._flags & os.O_EXCL: | ||
| raise ValueError("'name' can only be None if create=True") | ||
| self._track = track | ||
| if _USE_POSIX: | ||
| # POSIX Shared Memory | ||
| @@ -116,8 +118,8 @@ def __init__(self, name=None, create=False, size=0): | ||
| except OSError: | ||
| self.unlink() | ||
| raise | ||
| if self._track: | ||
| resource_tracker.register(self._name, "shared_memory") | ||
| else: | ||
| @@ -236,12 +238,20 @@ def close(self): | ||
| def unlink(self): | ||
| """Requests that the underlying shared memory block be destroyed. | ||
| Unlink should be called once (and only once) across all handles | ||
| which have access to the shared memory block, even if these | ||
| handles belong to different processes. Closing and unlinking may | ||
| happen in any order, but trying to access data inside a shared | ||
| memory block after unlinking may result in memory errors, | ||
| depending on platform. | ||
| This method has no effect on Windows, where the only way to | ||
| delete a shared memory block is to close all handles.""" | ||
| if _USE_POSIX and self._name: | ||
| _posixshmem.shm_unlink(self._name) | ||
| if self._track: | ||
| resource_tracker.unregister(self._name, "shared_memory") | ||
| _encoding = "utf8" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -4455,6 +4455,59 @@ def test_shared_memory_cleaned_after_process_termination(self): | ||
| "resource_tracker: There appear to be 1 leaked " | ||
| "shared_memory objects to clean up at shutdown", err) | ||
| @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") | ||
| def test_shared_memory_untracking(self): | ||
gpshead marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| # gh-82300: When a separate Python process accesses shared memory | ||
| # with track=False, it must not cause the memory to be deleted | ||
| # when terminating. | ||
| cmd = '''if 1: | ||
| import sys | ||
| from multiprocessing.shared_memory import SharedMemory | ||
| mem = SharedMemory(create=False, name=sys.argv[1], track=False) | ||
| mem.close() | ||
| ''' | ||
| mem = shared_memory.SharedMemory(create=True, size=10) | ||
| # The resource tracker shares pipes with the subprocess, and so | ||
| # err existing means that the tracker process has terminated now. | ||
| try: | ||
| rc, out, err = script_helper.assert_python_ok("-c", cmd, mem.name) | ||
| self.assertNotIn(b"resource_tracker", err) | ||
| self.assertEqual(rc, 0) | ||
| mem2 = shared_memory.SharedMemory(create=False, name=mem.name) | ||
gpshead marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| mem2.close() | ||
| finally: | ||
| try: | ||
| mem.unlink() | ||
| except OSError: | ||
| pass | ||
| mem.close() | ||
| @unittest.skipIf(os.name != "posix", "resource_tracker is posix only") | ||
| def test_shared_memory_tracking(self): | ||
| # gh-82300: When a separate Python process accesses shared memory | ||
| # with track=True, it must cause the memory to be deleted when | ||
| # terminating. | ||
| cmd = '''if 1: | ||
| import sys | ||
| from multiprocessing.shared_memory import SharedMemory | ||
| mem = SharedMemory(create=False, name=sys.argv[1], track=True) | ||
| mem.close() | ||
| ''' | ||
| mem = shared_memory.SharedMemory(create=True, size=10) | ||
| try: | ||
| rc, out, err = script_helper.assert_python_ok("-c", cmd, mem.name) | ||
| self.assertEqual(rc, 0) | ||
| self.assertIn( | ||
| b"resource_tracker: There appear to be 1 leaked " | ||
| b"shared_memory objects to clean up at shutdown", err) | ||
| finally: | ||
| try: | ||
| mem.unlink() | ||
| except OSError: | ||
gpshead marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
| pass | ||
| resource_tracker.unregister(mem._name, "shared_memory") | ||
| mem.close() | ||
| # | ||
| # Test to verify that `Finalize` works. | ||
| # | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Add ``track`` parameter to :class:`multiprocessing.shared_memory.SharedMemory` that allows using shared memory blocks without having to register with the POSIX resource tracker that automatically releases them upon process exit. |