Skip to content

Commit bd9e129

Browse files
committed
bpo-38119: fix shared memory's resource tracking
1 parent 3529718 commit bd9e129

File tree

8 files changed

+189
-12
lines changed

8 files changed

+189
-12
lines changed

Include/internal/pycore_atomic.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ typedef struct _Py_atomic_int {
6262
#define _Py_atomic_load_explicit(ATOMIC_VAL, ORDER) \
6363
atomic_load_explicit(&((ATOMIC_VAL)->_value), ORDER)
6464

65+
#define _Py_atomic_fetch_add_explicit(ATOMIC_VAL, VAL, ORDER) \
66+
atomic_fetch_add_explicit(&((ATOMIC_VAL)->_value), VAL, ORDER)
67+
68+
#define _Py_atomic_fetch_sub_explicit(ATOMIC_VAL, VAL, ORDER) \
69+
atomic_fetch_sub_explicit(&((ATOMIC_VAL)->_value), VAL, ORDER)
70+
6571
/* Use builtin atomic operations in GCC >= 4.7 */
6672
#elif defined(HAVE_BUILTIN_ATOMIC)
6773

@@ -100,6 +106,20 @@ typedef struct _Py_atomic_int {
100106
|| (ORDER) == __ATOMIC_CONSUME), \
101107
__atomic_load_n(&((ATOMIC_VAL)->_value), ORDER))
102108

109+
#define _Py_atomic_fetch_add_explicit(ATOMIC_VAL, VAL, ORDER) \
110+
(assert((ORDER) == __ATOMIC_RELAXED \
111+
|| (ORDER) == __ATOMIC_SEQ_CST \
112+
|| (ORDER) == __ATOMIC_ACQUIRE \
113+
|| (ORDER) == __ATOMIC_CONSUME), \
114+
__atomic_fetch_add(&((ATOMIC_VAL)->_value), VAL, ORDER))
115+
116+
#define _Py_atomic_fetch_sub_explicit(ATOMIC_VAL, VAL, ORDER) \
117+
(assert((ORDER) == __ATOMIC_RELAXED \
118+
|| (ORDER) == __ATOMIC_SEQ_CST \
119+
|| (ORDER) == __ATOMIC_ACQUIRE \
120+
|| (ORDER) == __ATOMIC_CONSUME), \
121+
__atomic_fetch_sub(&((ATOMIC_VAL)->_value), VAL, ORDER))
122+
103123
/* Only support GCC (for expression statements) and x86 (for simple
104124
* atomic semantics) and MSVC x86/x64/ARM */
105125
#elif defined(__GNUC__) && (defined(__i386__) || defined(__amd64))
@@ -551,6 +571,12 @@ typedef struct _Py_atomic_int {
551571
#define _Py_atomic_load_relaxed(ATOMIC_VAL) \
552572
_Py_atomic_load_explicit((ATOMIC_VAL), _Py_memory_order_relaxed)
553573

574+
#define _Py_atomic_fetch_add_relaxed(ATOMIC_VAL, VAL) \
575+
_Py_atomic_fetch_add_explicit((ATOMIC_VAL), (VAL), _Py_memory_order_relaxed)
576+
577+
#define _Py_atomic_fetch_sub_relaxed(ATOMIC_VAL, VAL) \
578+
_Py_atomic_fetch_sub_explicit((ATOMIC_VAL), (VAL), _Py_memory_order_relaxed)
579+
554580
#ifdef __cplusplus
555581
}
556582
#endif

Lib/multiprocessing/resource_tracker.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@
3636
if os.name == 'posix':
3737
import _multiprocessing
3838
import _posixshmem
39+
from multiprocessing.shared_memory import cleanup_shared_memory, shm_inc_refcount
3940

4041
_CLEANUP_FUNCS.update({
4142
'semaphore': _multiprocessing.sem_unlink,
42-
'shared_memory': _posixshmem.shm_unlink,
43+
'shared_memory': cleanup_shared_memory,
4344
})
4445

4546

@@ -196,7 +197,9 @@ def main(fd):
196197
f'unknown resource type {rtype}')
197198

198199
if cmd == 'REGISTER':
199-
cache[rtype].add(name)
200+
if rtype == "shared_memory" and name not in cache[rtype]:
201+
cache[rtype].add(name)
202+
shm_inc_refcount(name)
200203
elif cmd == 'UNREGISTER':
201204
cache[rtype].remove(name)
202205
elif cmd == 'PROBE':

Lib/multiprocessing/shared_memory.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ class SharedMemory:
7070
_flags = os.O_RDWR
7171
_mode = 0o600
7272
_prepend_leading_slash = True if _USE_POSIX else False
73+
_track_resource = True
7374

74-
def __init__(self, name=None, create=False, size=0):
75+
def __init__(self, name=None, create=False, size=0, track_resource=True):
7576
if not size >= 0:
7677
raise ValueError("'size' must be a positive integer")
7778
if create:
@@ -81,6 +82,7 @@ def __init__(self, name=None, create=False, size=0):
8182
if name is None and not self._flags & os.O_EXCL:
8283
raise ValueError("'name' can only be None if create=True")
8384

85+
self._track_resource = track_resource
8486
if _USE_POSIX:
8587

8688
# POSIX Shared Memory
@@ -108,6 +110,7 @@ def __init__(self, name=None, create=False, size=0):
108110
self._name = name
109111
try:
110112
if create and size:
113+
size += _posixshmem.REFCOUNT_SIZE
111114
os.ftruncate(self._fd, size)
112115
stats = os.fstat(self._fd)
113116
size = stats.st_size
@@ -116,8 +119,13 @@ def __init__(self, name=None, create=False, size=0):
116119
self.unlink()
117120
raise
118121

119-
from .resource_tracker import register
120-
register(self._name, "shared_memory")
122+
self._size = size
123+
self._refcount = memoryview(self._mmap)[0:_posixshmem.REFCOUNT_SIZE]
124+
self._buf = memoryview(self._mmap)[_posixshmem.REFCOUNT_SIZE:]
125+
126+
if self._track_resource:
127+
from .resource_tracker import register
128+
register(self.name, "shared_memory")
121129

122130
else:
123131

@@ -176,8 +184,9 @@ def __init__(self, name=None, create=False, size=0):
176184
size = _winapi.VirtualQuerySize(p_buf)
177185
self._mmap = mmap.mmap(-1, size, tagname=name)
178186

179-
self._size = size
180-
self._buf = memoryview(self._mmap)
187+
self._size = size
188+
self._buf = memoryview(self._mmap)
189+
self._refcount = None
181190

182191
def __del__(self):
183192
try:
@@ -215,11 +224,17 @@ def name(self):
215224
@property
216225
def size(self):
217226
"Size in bytes."
218-
return self._size
227+
if _USE_POSIX:
228+
return self._size - _posixshmem.REFCOUNT_SIZE
229+
else:
230+
return self._size
219231

220232
def close(self):
221233
"""Closes access to the shared memory from this instance but does
222234
not destroy the shared memory block."""
235+
if self._refcount is not None:
236+
self._refcount.release()
237+
self._refcount = None
223238
if self._buf is not None:
224239
self._buf.release()
225240
self._buf = None
@@ -237,10 +252,25 @@ def unlink(self):
237252
called once (and only once) across all processes which have access
238253
to the shared memory block."""
239254
if _USE_POSIX and self._name:
240-
from .resource_tracker import unregister
241255
_posixshmem.shm_unlink(self._name)
242-
unregister(self._name, "shared_memory")
243-
256+
if self._track_resource:
257+
from .resource_tracker import unregister
258+
unregister(self.name, "shared_memory")
259+
260+
def cleanup_shared_memory(name):
261+
try:
262+
shm = SharedMemory(name, track_resource=False)
263+
refcount = _posixshmem.shm_dec_refcount(shm._refcount)
264+
if refcount == 0: shm.unlink()
265+
except FileNotFoundError: # Segment with name has already been unlinked
266+
pass
267+
268+
def shm_inc_refcount(name):
269+
try:
270+
shm = SharedMemory(name, track_resource=False)
271+
_posixshmem.shm_inc_refcount(shm._refcount)
272+
except FileNotFoundError: # Segment with name has already been unlinked
273+
pass
244274

245275
_encoding = "utf8"
246276

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix shared_memory's resource tracking by using reference counting

Modules/Setup

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ _signal -DPy_BUILD_CORE_BUILTIN -I$(srcdir)/Include/internal signalmodule.c
118118
_stat _stat.c # stat.h interface
119119
time -DPy_BUILD_CORE_BUILTIN -I$(srcdir)/Include/internal timemodule.c # -lm # time operations and variables
120120
_thread -DPy_BUILD_CORE_BUILTIN -I$(srcdir)/Include/internal _threadmodule.c # low-level threading interface
121+
_posixshmem -DPy_BUILD_CORE_BUILTIN -I$(srcdir)/Include/internal _multiprocessing/posixshmem.c # shared memory interface dependent on 'pycore_atomic.h'
121122

122123
# access to ISO C locale support
123124
_locale -DPy_BUILD_CORE_BUILTIN _localemodule.c # -lintl

Modules/_multiprocessing/clinic/posixshmem.c.h

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,85 @@ _posixshmem_shm_unlink(PyObject *module, PyObject *const *args, Py_ssize_t nargs
113113

114114
#endif /* defined(HAVE_SHM_UNLINK) */
115115

116+
PyDoc_STRVAR(_posixshmem_shm_inc_refcount__doc__,
117+
"shm_inc_refcount($module, /, ptr)\n"
118+
"--\n"
119+
"\n"
120+
"Increment Reference Count of the memoryview object");
121+
122+
#define _POSIXSHMEM_SHM_INC_REFCOUNT_METHODDEF \
123+
{"shm_inc_refcount", (PyCFunction)(void(*)(void))_posixshmem_shm_inc_refcount, METH_FASTCALL|METH_KEYWORDS, _posixshmem_shm_inc_refcount__doc__},
124+
125+
static int
126+
_posixshmem_shm_inc_refcount_impl(PyObject *module, PyObject *ptr);
127+
128+
static PyObject *
129+
_posixshmem_shm_inc_refcount(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
130+
{
131+
PyObject *return_value = NULL;
132+
static const char * const _keywords[] = {"ptr", NULL};
133+
static _PyArg_Parser _parser = {NULL, _keywords, "shm_inc_refcount", 0};
134+
PyObject *argsbuf[1];
135+
PyObject *ptr;
136+
int _return_value;
137+
138+
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
139+
if (!args) {
140+
goto exit;
141+
}
142+
ptr = args[0];
143+
_return_value = _posixshmem_shm_inc_refcount_impl(module, ptr);
144+
if ((_return_value == -1) && PyErr_Occurred()) {
145+
goto exit;
146+
}
147+
return_value = PyLong_FromLong((long)_return_value);
148+
149+
exit:
150+
return return_value;
151+
}
152+
153+
PyDoc_STRVAR(_posixshmem_shm_dec_refcount__doc__,
154+
"shm_dec_refcount($module, /, ptr)\n"
155+
"--\n"
156+
"\n"
157+
"Decrement Reference Count of the memoryview object");
158+
159+
#define _POSIXSHMEM_SHM_DEC_REFCOUNT_METHODDEF \
160+
{"shm_dec_refcount", (PyCFunction)(void(*)(void))_posixshmem_shm_dec_refcount, METH_FASTCALL|METH_KEYWORDS, _posixshmem_shm_dec_refcount__doc__},
161+
162+
static int
163+
_posixshmem_shm_dec_refcount_impl(PyObject *module, PyObject *ptr);
164+
165+
static PyObject *
166+
_posixshmem_shm_dec_refcount(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
167+
{
168+
PyObject *return_value = NULL;
169+
static const char * const _keywords[] = {"ptr", NULL};
170+
static _PyArg_Parser _parser = {NULL, _keywords, "shm_dec_refcount", 0};
171+
PyObject *argsbuf[1];
172+
PyObject *ptr;
173+
int _return_value;
174+
175+
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
176+
if (!args) {
177+
goto exit;
178+
}
179+
ptr = args[0];
180+
_return_value = _posixshmem_shm_dec_refcount_impl(module, ptr);
181+
if ((_return_value == -1) && PyErr_Occurred()) {
182+
goto exit;
183+
}
184+
return_value = PyLong_FromLong((long)_return_value);
185+
186+
exit:
187+
return return_value;
188+
}
189+
116190
#ifndef _POSIXSHMEM_SHM_OPEN_METHODDEF
117191
#define _POSIXSHMEM_SHM_OPEN_METHODDEF
118192
#endif /* !defined(_POSIXSHMEM_SHM_OPEN_METHODDEF) */
119193

120194
#ifndef _POSIXSHMEM_SHM_UNLINK_METHODDEF
121195
#define _POSIXSHMEM_SHM_UNLINK_METHODDEF
122196
#endif /* !defined(_POSIXSHMEM_SHM_UNLINK_METHODDEF) */
123-
/*[clinic end generated code: output=bca8e78d0f43ef1a input=a9049054013a1b77]*/
197+
/*[clinic end generated code: output=cd4bd3692d1ce532 input=a9049054013a1b77]*/

Modules/_multiprocessing/posixshmem.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ posixshmem - A Python extension that provides shm_open() and shm_unlink()
55
#define PY_SSIZE_T_CLEAN
66

77
#include <Python.h>
8+
#include <stdio.h>
9+
#include "pycore_atomic.h"
810

911
// for shm_open() and shm_unlink()
1012
#ifdef HAVE_SYS_MMAN_H
@@ -101,11 +103,44 @@ _posixshmem_shm_unlink_impl(PyObject *module, PyObject *path)
101103
}
102104
#endif /* HAVE_SHM_UNLINK */
103105

106+
/*[clinic input]
107+
_posixshmem.shm_inc_refcount -> int
108+
ptr: object
109+
110+
Increment Reference Count of the memoryview object
111+
112+
[clinic start generated code]*/
113+
114+
static int
115+
_posixshmem_shm_inc_refcount_impl(PyObject *module, PyObject *ptr)
116+
/*[clinic end generated code: output=9ed5b4d016975d06 input=b7c1fe6ce39b7bb4]*/
117+
{
118+
Py_buffer *buf = PyMemoryView_GET_BUFFER(ptr);
119+
return _Py_atomic_fetch_add_relaxed((_Py_atomic_int*)(buf->buf), 1) + 1;
120+
}
121+
/*[clinic input]
122+
_posixshmem.shm_dec_refcount -> int
123+
ptr: object
124+
125+
Decrement Reference Count of the memoryview object
126+
127+
[clinic start generated code]*/
128+
129+
static int
130+
_posixshmem_shm_dec_refcount_impl(PyObject *module, PyObject *ptr)
131+
/*[clinic end generated code: output=16ab284487281c72 input=0aab6ded127aa5c3]*/
132+
{
133+
Py_buffer *buf = PyMemoryView_GET_BUFFER(ptr);
134+
return _Py_atomic_fetch_sub_relaxed((_Py_atomic_int*)(buf->buf), 1) - 1;
135+
}
136+
104137
#include "clinic/posixshmem.c.h"
105138

106139
static PyMethodDef module_methods[ ] = {
107140
_POSIXSHMEM_SHM_OPEN_METHODDEF
108141
_POSIXSHMEM_SHM_UNLINK_METHODDEF
142+
_POSIXSHMEM_SHM_INC_REFCOUNT_METHODDEF
143+
_POSIXSHMEM_SHM_DEC_REFCOUNT_METHODDEF
109144
{NULL} /* Sentinel */
110145
};
111146

@@ -118,6 +153,8 @@ static struct PyModuleDef this_module = {
118153
module_methods, // m_methods
119154
};
120155

156+
const char *NAME_REFCOUNT_SIZE = "REFCOUNT_SIZE";
157+
121158
/* Module init function */
122159
PyMODINIT_FUNC
123160
PyInit__posixshmem(void) {
@@ -126,5 +163,9 @@ PyInit__posixshmem(void) {
126163
if (!module) {
127164
return NULL;
128165
}
166+
if (PyModule_AddIntConstant(module, NAME_REFCOUNT_SIZE, sizeof(_Py_atomic_int))) {
167+
Py_XDECREF(module);
168+
return NULL;
169+
}
129170
return module;
130171
}

Tools/c-analyzer/cpython/_parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def clean_lines(text):
150150
Modules/main.c Py_BUILD_CORE 1
151151
Modules/posixmodule.c Py_BUILD_CORE 1
152152
Modules/signalmodule.c Py_BUILD_CORE 1
153+
Modules/_multiprocessing/posixshmem.c Py_BUILD_CORE 1
153154
Modules/_threadmodule.c Py_BUILD_CORE 1
154155
Modules/_tracemalloc.c Py_BUILD_CORE 1
155156
Modules/_asynciomodule.c Py_BUILD_CORE 1

0 commit comments

Comments
 (0)