mirror of https://github.com/python/cpython
#3712: The memoryview object had a reference leak and didn't support cyclic garbage collection.
Reviewed by Benjamin Peterson.
This commit is contained in:
parent
d26782e863
commit
c6b09ebe58
|
@ -6,6 +6,8 @@ XXX We need more tests! Some tests are in test_bytes
|
||||||
import unittest
|
import unittest
|
||||||
import test.support
|
import test.support
|
||||||
import sys
|
import sys
|
||||||
|
import gc
|
||||||
|
import weakref
|
||||||
|
|
||||||
|
|
||||||
class CommonMemoryTests:
|
class CommonMemoryTests:
|
||||||
|
@ -157,6 +159,36 @@ class CommonMemoryTests:
|
||||||
m = self.check_attributes_with_type(bytearray)
|
m = self.check_attributes_with_type(bytearray)
|
||||||
self.assertEquals(m.readonly, False)
|
self.assertEquals(m.readonly, False)
|
||||||
|
|
||||||
|
def test_getbuffer(self):
|
||||||
|
# Test PyObject_GetBuffer() on a memoryview object.
|
||||||
|
b = self.base_object
|
||||||
|
oldrefcount = sys.getrefcount(b)
|
||||||
|
m = self._view(b)
|
||||||
|
oldviewrefcount = sys.getrefcount(m)
|
||||||
|
s = str(m, "utf-8")
|
||||||
|
self._check_contents(b, s.encode("utf-8"))
|
||||||
|
self.assertEquals(sys.getrefcount(m), oldviewrefcount)
|
||||||
|
m = None
|
||||||
|
self.assertEquals(sys.getrefcount(b), oldrefcount)
|
||||||
|
|
||||||
|
def test_gc(self):
|
||||||
|
class MyBytes(bytes):
|
||||||
|
pass
|
||||||
|
class MyObject:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Create a reference cycle through a memoryview object
|
||||||
|
b = MyBytes(b'abc')
|
||||||
|
m = self._view(b)
|
||||||
|
o = MyObject()
|
||||||
|
b.m = m
|
||||||
|
b.o = o
|
||||||
|
wr = weakref.ref(o)
|
||||||
|
b = m = o = None
|
||||||
|
# The cycle must be broken
|
||||||
|
gc.collect()
|
||||||
|
self.assert_(wr() is None, wr())
|
||||||
|
|
||||||
|
|
||||||
class MemoryviewTest(unittest.TestCase, CommonMemoryTests):
|
class MemoryviewTest(unittest.TestCase, CommonMemoryTests):
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@ What's New in Python 3.0 release candidate 1
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #3712: The memoryview object had a reference leak and didn't support
|
||||||
|
cyclic garbage collection.
|
||||||
|
|
||||||
- Issue #3668: Fix a memory leak with the "s*" argument parser in
|
- Issue #3668: Fix a memory leak with the "s*" argument parser in
|
||||||
PyArg_ParseTuple and friends, which occurred when the argument for "s*"
|
PyArg_ParseTuple and friends, which occurred when the argument for "s*"
|
||||||
was correctly parsed but parsing of subsequent arguments failed.
|
was correctly parsed but parsing of subsequent arguments failed.
|
||||||
|
|
|
@ -3,24 +3,34 @@
|
||||||
|
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
|
||||||
|
static void
|
||||||
|
dup_buffer(Py_buffer *dest, Py_buffer *src)
|
||||||
|
{
|
||||||
|
*dest = *src;
|
||||||
|
if (src->shape == &(src->len))
|
||||||
|
dest->shape = &(dest->len);
|
||||||
|
if (src->strides == &(src->itemsize))
|
||||||
|
dest->strides = &(dest->itemsize);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags)
|
memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags)
|
||||||
{
|
{
|
||||||
if (view != NULL) {
|
int res = 0;
|
||||||
if (self->view.obj)
|
/* XXX for whatever reason fixing the flags seems necessary */
|
||||||
Py_INCREF(self->view.obj);
|
if (self->view.readonly)
|
||||||
*view = self->view;
|
flags &= ~PyBUF_WRITABLE;
|
||||||
}
|
if (self->view.obj != NULL)
|
||||||
if (self->view.obj == NULL)
|
res = PyObject_GetBuffer(self->view.obj, view, flags);
|
||||||
return 0;
|
if (view)
|
||||||
return self->view.obj->ob_type->tp_as_buffer->bf_getbuffer(
|
dup_buffer(view, &self->view);
|
||||||
self->view.obj, NULL, PyBUF_FULL);
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
memory_releasebuf(PyMemoryViewObject *self, Py_buffer *view)
|
memory_releasebuf(PyMemoryViewObject *self, Py_buffer *view)
|
||||||
{
|
{
|
||||||
PyBuffer_Release(&self->view);
|
PyBuffer_Release(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(memory_doc,
|
PyDoc_STRVAR(memory_doc,
|
||||||
|
@ -33,18 +43,15 @@ PyMemoryView_FromBuffer(Py_buffer *info)
|
||||||
{
|
{
|
||||||
PyMemoryViewObject *mview;
|
PyMemoryViewObject *mview;
|
||||||
|
|
||||||
mview = (PyMemoryViewObject *)PyObject_New(PyMemoryViewObject,
|
mview = (PyMemoryViewObject *)
|
||||||
&PyMemoryView_Type);
|
PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type);
|
||||||
if (mview == NULL) return NULL;
|
if (mview == NULL)
|
||||||
|
return NULL;
|
||||||
mview->base = NULL;
|
mview->base = NULL;
|
||||||
/* XXX there should be an API to duplicate a buffer object */
|
dup_buffer(&mview->view, info);
|
||||||
mview->view = *info;
|
|
||||||
if (info->shape == &(info->len))
|
|
||||||
mview->view.shape = &(mview->view.len);
|
|
||||||
if (info->strides == &(info->itemsize))
|
|
||||||
mview->view.strides = &(mview->view.itemsize);
|
|
||||||
/* NOTE: mview->view.obj should already have been incref'ed as
|
/* NOTE: mview->view.obj should already have been incref'ed as
|
||||||
part of PyBuffer_FillInfo(). */
|
part of PyBuffer_FillInfo(). */
|
||||||
|
_PyObject_GC_TRACK(mview);
|
||||||
return (PyObject *)mview;
|
return (PyObject *)mview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,9 +67,10 @@ PyMemoryView_FromObject(PyObject *base)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
mview = (PyMemoryViewObject *)PyObject_New(PyMemoryViewObject,
|
mview = (PyMemoryViewObject *)
|
||||||
&PyMemoryView_Type);
|
PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type);
|
||||||
if (mview == NULL) return NULL;
|
if (mview == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
mview->base = NULL;
|
mview->base = NULL;
|
||||||
if (PyObject_GetBuffer(base, &(mview->view), PyBUF_FULL_RO) < 0) {
|
if (PyObject_GetBuffer(base, &(mview->view), PyBUF_FULL_RO) < 0) {
|
||||||
|
@ -72,6 +80,7 @@ PyMemoryView_FromObject(PyObject *base)
|
||||||
|
|
||||||
mview->base = base;
|
mview->base = base;
|
||||||
Py_INCREF(base);
|
Py_INCREF(base);
|
||||||
|
_PyObject_GC_TRACK(mview);
|
||||||
return (PyObject *)mview;
|
return (PyObject *)mview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,8 +242,9 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
mem = PyObject_New(PyMemoryViewObject, &PyMemoryView_Type);
|
mem = PyObject_GC_New(PyMemoryViewObject, &PyMemoryView_Type);
|
||||||
if (mem == NULL) return NULL;
|
if (mem == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
view = &mem->view;
|
view = &mem->view;
|
||||||
flags = PyBUF_FULL_RO;
|
flags = PyBUF_FULL_RO;
|
||||||
|
@ -245,7 +255,7 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyObject_GetBuffer(obj, view, flags) != 0) {
|
if (PyObject_GetBuffer(obj, view, flags) != 0) {
|
||||||
PyObject_DEL(mem);
|
Py_DECREF(mem);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,11 +263,12 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort)
|
||||||
/* no copy needed */
|
/* no copy needed */
|
||||||
Py_INCREF(obj);
|
Py_INCREF(obj);
|
||||||
mem->base = obj;
|
mem->base = obj;
|
||||||
|
_PyObject_GC_TRACK(mem);
|
||||||
return (PyObject *)mem;
|
return (PyObject *)mem;
|
||||||
}
|
}
|
||||||
/* otherwise a copy is needed */
|
/* otherwise a copy is needed */
|
||||||
if (buffertype == PyBUF_WRITE) {
|
if (buffertype == PyBUF_WRITE) {
|
||||||
PyObject_DEL(mem);
|
Py_DECREF(mem);
|
||||||
PyErr_SetString(PyExc_BufferError,
|
PyErr_SetString(PyExc_BufferError,
|
||||||
"writable contiguous buffer requested "
|
"writable contiguous buffer requested "
|
||||||
"for a non-contiguousobject.");
|
"for a non-contiguousobject.");
|
||||||
|
@ -265,7 +276,7 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort)
|
||||||
}
|
}
|
||||||
bytes = PyBytes_FromStringAndSize(NULL, view->len);
|
bytes = PyBytes_FromStringAndSize(NULL, view->len);
|
||||||
if (bytes == NULL) {
|
if (bytes == NULL) {
|
||||||
PyBuffer_Release(view);
|
Py_DECREF(mem);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
dest = PyBytes_AS_STRING(bytes);
|
dest = PyBytes_AS_STRING(bytes);
|
||||||
|
@ -280,7 +291,7 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort)
|
||||||
else {
|
else {
|
||||||
if (_indirect_copy_nd(dest, view, fort) < 0) {
|
if (_indirect_copy_nd(dest, view, fort) < 0) {
|
||||||
Py_DECREF(bytes);
|
Py_DECREF(bytes);
|
||||||
PyBuffer_Release(view);
|
Py_DECREF(mem);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,15 +301,16 @@ PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char fort)
|
||||||
mem->base = PyTuple_Pack(2, obj, bytes);
|
mem->base = PyTuple_Pack(2, obj, bytes);
|
||||||
Py_DECREF(bytes);
|
Py_DECREF(bytes);
|
||||||
if (mem->base == NULL) {
|
if (mem->base == NULL) {
|
||||||
PyBuffer_Release(view);
|
Py_DECREF(mem);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PyBuffer_Release(view);
|
PyBuffer_Release(view); /* XXX ? */
|
||||||
/* steal the reference */
|
/* steal the reference */
|
||||||
mem->base = bytes;
|
mem->base = bytes;
|
||||||
}
|
}
|
||||||
|
_PyObject_GC_TRACK(mem);
|
||||||
return (PyObject *)mem;
|
return (PyObject *)mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,6 +456,7 @@ static PyMethodDef memory_methods[] = {
|
||||||
static void
|
static void
|
||||||
memory_dealloc(PyMemoryViewObject *self)
|
memory_dealloc(PyMemoryViewObject *self)
|
||||||
{
|
{
|
||||||
|
_PyObject_GC_UNTRACK(self);
|
||||||
if (self->view.obj != NULL) {
|
if (self->view.obj != NULL) {
|
||||||
if (self->base && PyTuple_Check(self->base)) {
|
if (self->base && PyTuple_Check(self->base)) {
|
||||||
/* Special case when first element is generic object
|
/* Special case when first element is generic object
|
||||||
|
@ -468,7 +481,7 @@ memory_dealloc(PyMemoryViewObject *self)
|
||||||
}
|
}
|
||||||
Py_CLEAR(self->base);
|
Py_CLEAR(self->base);
|
||||||
}
|
}
|
||||||
PyObject_DEL(self);
|
PyObject_GC_Del(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -749,6 +762,25 @@ _notimpl:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int
|
||||||
|
memory_traverse(PyMemoryViewObject *self, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
if (self->base != NULL)
|
||||||
|
Py_VISIT(self->base);
|
||||||
|
if (self->view.obj != NULL)
|
||||||
|
Py_VISIT(self->view.obj);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
memory_clear(PyMemoryViewObject *self)
|
||||||
|
{
|
||||||
|
Py_CLEAR(self->base);
|
||||||
|
PyBuffer_Release(&self->view);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* As mapping */
|
/* As mapping */
|
||||||
static PyMappingMethods memory_as_mapping = {
|
static PyMappingMethods memory_as_mapping = {
|
||||||
(lenfunc)memory_length, /*mp_length*/
|
(lenfunc)memory_length, /*mp_length*/
|
||||||
|
@ -785,10 +817,10 @@ PyTypeObject PyMemoryView_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
&memory_as_buffer, /* tp_as_buffer */
|
&memory_as_buffer, /* tp_as_buffer */
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
|
||||||
memory_doc, /* tp_doc */
|
memory_doc, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
(traverseproc)memory_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
(inquiry)memory_clear, /* tp_clear */
|
||||||
memory_richcompare, /* tp_richcompare */
|
memory_richcompare, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
0, /* tp_iter */
|
0, /* tp_iter */
|
||||||
|
|
Loading…
Reference in New Issue