mirror of https://github.com/python/cpython
gh-77894: Fix a crash when the GC breaks a loop containing a memoryview (GH-123898)
Now a memoryview object can only be cleared if there are no buffers that refer it.
This commit is contained in:
parent
00ffdf2736
commit
a1dbf2ea69
|
@ -2323,12 +2323,10 @@ class AbstractPicklingErrorTests:
|
||||||
'PickleBuffer can only be pickled with protocol >= 5')
|
'PickleBuffer can only be pickled with protocol >= 5')
|
||||||
|
|
||||||
def test_non_continuous_buffer(self):
|
def test_non_continuous_buffer(self):
|
||||||
if self.pickler is pickle._Pickler:
|
|
||||||
self.skipTest('CRASHES (see gh-122306)')
|
|
||||||
for proto in protocols[5:]:
|
for proto in protocols[5:]:
|
||||||
with self.subTest(proto=proto):
|
with self.subTest(proto=proto):
|
||||||
pb = pickle.PickleBuffer(memoryview(b"foobar")[::2])
|
pb = pickle.PickleBuffer(memoryview(b"foobar")[::2])
|
||||||
with self.assertRaises(pickle.PicklingError):
|
with self.assertRaises((pickle.PicklingError, BufferError)):
|
||||||
self.dumps(pb, proto)
|
self.dumps(pb, proto)
|
||||||
|
|
||||||
def test_buffer_callback_error(self):
|
def test_buffer_callback_error(self):
|
||||||
|
|
|
@ -18,6 +18,10 @@ import struct
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
|
|
||||||
|
|
||||||
|
class MyObject:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AbstractMemoryTests:
|
class AbstractMemoryTests:
|
||||||
source_bytes = b"abcdef"
|
source_bytes = b"abcdef"
|
||||||
|
|
||||||
|
@ -228,8 +232,6 @@ class AbstractMemoryTests:
|
||||||
self.m = memoryview(base)
|
self.m = memoryview(base)
|
||||||
class MySource(tp):
|
class MySource(tp):
|
||||||
pass
|
pass
|
||||||
class MyObject:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Create a reference cycle through a memoryview object.
|
# Create a reference cycle through a memoryview object.
|
||||||
# This exercises mbuf_clear().
|
# This exercises mbuf_clear().
|
||||||
|
@ -656,5 +658,26 @@ class OtherTest(unittest.TestCase):
|
||||||
m[0] = MyBool()
|
m[0] = MyBool()
|
||||||
self.assertEqual(ba[:8], b'\0'*8)
|
self.assertEqual(ba[:8], b'\0'*8)
|
||||||
|
|
||||||
|
def test_buffer_reference_loop(self):
|
||||||
|
m = memoryview(b'abc').__buffer__(0)
|
||||||
|
o = MyObject()
|
||||||
|
o.m = m
|
||||||
|
o.o = o
|
||||||
|
wr = weakref.ref(o)
|
||||||
|
del m, o
|
||||||
|
gc.collect()
|
||||||
|
self.assertIsNone(wr())
|
||||||
|
|
||||||
|
def test_picklebuffer_reference_loop(self):
|
||||||
|
pb = pickle.PickleBuffer(memoryview(b'abc'))
|
||||||
|
o = MyObject()
|
||||||
|
o.pb = pb
|
||||||
|
o.o = o
|
||||||
|
wr = weakref.ref(o)
|
||||||
|
del pb, o
|
||||||
|
gc.collect()
|
||||||
|
self.assertIsNone(wr())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Fix possible crash in the garbage collector when it tries to break a
|
||||||
|
reference loop containing a :class:`memoryview` object. Now a
|
||||||
|
:class:`!memoryview` object can only be cleared if there are no buffers that
|
||||||
|
refer it.
|
|
@ -109,8 +109,6 @@ mbuf_release(_PyManagedBufferObject *self)
|
||||||
if (self->flags&_Py_MANAGED_BUFFER_RELEASED)
|
if (self->flags&_Py_MANAGED_BUFFER_RELEASED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* NOTE: at this point self->exports can still be > 0 if this function
|
|
||||||
is called from mbuf_clear() to break up a reference cycle. */
|
|
||||||
self->flags |= _Py_MANAGED_BUFFER_RELEASED;
|
self->flags |= _Py_MANAGED_BUFFER_RELEASED;
|
||||||
|
|
||||||
/* PyBuffer_Release() decrements master->obj and sets it to NULL. */
|
/* PyBuffer_Release() decrements master->obj and sets it to NULL. */
|
||||||
|
@ -1096,32 +1094,19 @@ PyBuffer_ToContiguous(void *buf, const Py_buffer *src, Py_ssize_t len, char orde
|
||||||
/* Inform the managed buffer that this particular memoryview will not access
|
/* Inform the managed buffer that this particular memoryview will not access
|
||||||
the underlying buffer again. If no other memoryviews are registered with
|
the underlying buffer again. If no other memoryviews are registered with
|
||||||
the managed buffer, the underlying buffer is released instantly and
|
the managed buffer, the underlying buffer is released instantly and
|
||||||
marked as inaccessible for both the memoryview and the managed buffer.
|
marked as inaccessible for both the memoryview and the managed buffer. */
|
||||||
|
static void
|
||||||
This function fails if the memoryview itself has exported buffers. */
|
|
||||||
static int
|
|
||||||
_memory_release(PyMemoryViewObject *self)
|
_memory_release(PyMemoryViewObject *self)
|
||||||
{
|
{
|
||||||
|
assert(self->exports == 0);
|
||||||
if (self->flags & _Py_MEMORYVIEW_RELEASED)
|
if (self->flags & _Py_MEMORYVIEW_RELEASED)
|
||||||
return 0;
|
return;
|
||||||
|
|
||||||
if (self->exports == 0) {
|
self->flags |= _Py_MEMORYVIEW_RELEASED;
|
||||||
self->flags |= _Py_MEMORYVIEW_RELEASED;
|
assert(self->mbuf->exports > 0);
|
||||||
assert(self->mbuf->exports > 0);
|
if (--self->mbuf->exports == 0) {
|
||||||
if (--self->mbuf->exports == 0)
|
mbuf_release(self->mbuf);
|
||||||
mbuf_release(self->mbuf);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (self->exports > 0) {
|
|
||||||
PyErr_Format(PyExc_BufferError,
|
|
||||||
"memoryview has %zd exported buffer%s", self->exports,
|
|
||||||
self->exports==1 ? "" : "s");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyErr_SetString(PyExc_SystemError,
|
|
||||||
"_memory_release(): negative export count");
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
@ -1134,9 +1119,21 @@ static PyObject *
|
||||||
memoryview_release_impl(PyMemoryViewObject *self)
|
memoryview_release_impl(PyMemoryViewObject *self)
|
||||||
/*[clinic end generated code: output=d0b7e3ba95b7fcb9 input=bc71d1d51f4a52f0]*/
|
/*[clinic end generated code: output=d0b7e3ba95b7fcb9 input=bc71d1d51f4a52f0]*/
|
||||||
{
|
{
|
||||||
if (_memory_release(self) < 0)
|
if (self->exports == 0) {
|
||||||
|
_memory_release(self);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->exports > 0) {
|
||||||
|
PyErr_Format(PyExc_BufferError,
|
||||||
|
"memoryview has %zd exported buffer%s", self->exports,
|
||||||
|
self->exports==1 ? "" : "s");
|
||||||
return NULL;
|
return NULL;
|
||||||
Py_RETURN_NONE;
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_SystemError,
|
||||||
|
"memoryview: negative export count");
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1145,7 +1142,7 @@ memory_dealloc(PyObject *_self)
|
||||||
PyMemoryViewObject *self = (PyMemoryViewObject *)_self;
|
PyMemoryViewObject *self = (PyMemoryViewObject *)_self;
|
||||||
assert(self->exports == 0);
|
assert(self->exports == 0);
|
||||||
_PyObject_GC_UNTRACK(self);
|
_PyObject_GC_UNTRACK(self);
|
||||||
(void)_memory_release(self);
|
_memory_release(self);
|
||||||
Py_CLEAR(self->mbuf);
|
Py_CLEAR(self->mbuf);
|
||||||
if (self->weakreflist != NULL)
|
if (self->weakreflist != NULL)
|
||||||
PyObject_ClearWeakRefs((PyObject *) self);
|
PyObject_ClearWeakRefs((PyObject *) self);
|
||||||
|
@ -1164,8 +1161,10 @@ static int
|
||||||
memory_clear(PyObject *_self)
|
memory_clear(PyObject *_self)
|
||||||
{
|
{
|
||||||
PyMemoryViewObject *self = (PyMemoryViewObject *)_self;
|
PyMemoryViewObject *self = (PyMemoryViewObject *)_self;
|
||||||
(void)_memory_release(self);
|
if (self->exports == 0) {
|
||||||
Py_CLEAR(self->mbuf);
|
_memory_release(self);
|
||||||
|
Py_CLEAR(self->mbuf);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue