Skip to content

Commit 491c234

Browse files
committed
Fix buffer resource management
1 parent 1c515fd commit 491c234

File tree

7 files changed

+123
-95
lines changed

7 files changed

+123
-95
lines changed

graalpython/com.oracle.graal.python.cext/src/memoryobject.c

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,45 @@
4040
*/
4141
#include "capi.h"
4242

43+
#if SIZEOF_SIZE_T == 8
44+
#define polyglot_from_size_array polyglot_from_i64_array
45+
#elif SIZEOF_SIZE_T == 4
46+
#define polyglot_from_size_array polyglot_from_i32_array
47+
#endif
48+
49+
/* Macros taken from CPython */
50+
/* Memoryview buffer properties */
51+
#define MV_C_CONTIGUOUS(flags) (flags&(_Py_MEMORYVIEW_SCALAR|_Py_MEMORYVIEW_C))
52+
#define MV_F_CONTIGUOUS(flags) \
53+
(flags&(_Py_MEMORYVIEW_SCALAR|_Py_MEMORYVIEW_FORTRAN))
54+
#define MV_ANY_CONTIGUOUS(flags) \
55+
(flags&(_Py_MEMORYVIEW_SCALAR|_Py_MEMORYVIEW_C|_Py_MEMORYVIEW_FORTRAN))
56+
57+
/* getbuffer() requests */
58+
#define REQ_INDIRECT(flags) ((flags&PyBUF_INDIRECT) == PyBUF_INDIRECT)
59+
#define REQ_C_CONTIGUOUS(flags) ((flags&PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS)
60+
#define REQ_F_CONTIGUOUS(flags) ((flags&PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS)
61+
#define REQ_ANY_CONTIGUOUS(flags) ((flags&PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS)
62+
#define REQ_STRIDES(flags) ((flags&PyBUF_STRIDES) == PyBUF_STRIDES)
63+
#define REQ_SHAPE(flags) ((flags&PyBUF_ND) == PyBUF_ND)
64+
#define REQ_WRITABLE(flags) (flags&PyBUF_WRITABLE)
65+
#define REQ_FORMAT(flags) (flags&PyBUF_FORMAT)
66+
67+
#define BASE_INACCESSIBLE(mv) \
68+
(((PyMemoryViewObject *)mv)->flags&_Py_MEMORYVIEW_RELEASED)
69+
#define CHECK_RELEASED(mv) \
70+
if (BASE_INACCESSIBLE(mv)) { \
71+
PyErr_SetString(PyExc_ValueError, \
72+
"operation forbidden on released memoryview object"); \
73+
return NULL; \
74+
}
75+
#define CHECK_RELEASED_INT(mv) \
76+
if (BASE_INACCESSIBLE(mv)) { \
77+
PyErr_SetString(PyExc_ValueError, \
78+
"operation forbidden on released memoryview object"); \
79+
return -1; \
80+
}
81+
4382
PyTypeObject PyMemoryView_Type = PY_TRUFFLE_TYPE_WITH_ITEMSIZE("memoryview", &PyType_Type, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, offsetof(PyMemoryViewObject, ob_array), sizeof(Py_ssize_t));
4483
PyTypeObject PyBuffer_Type = PY_TRUFFLE_TYPE("buffer", &PyType_Type, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, sizeof(PyBufferDecorator));
4584

@@ -59,13 +98,36 @@ PyObject* PyMemoryView_FromObject(PyObject *v) {
5998

6099
/* called back from the above upcall only if the object was native */
61100
PyObject* PyTruffle_MemoryViewFromObject(PyObject *v) {
62-
Py_buffer buffer;
63101
if (PyObject_CheckBuffer(v)) {
64-
PyObject *ret;
65-
if (PyObject_GetBuffer(v, &buffer, PyBUF_FULL_RO) < 0) {
102+
Py_buffer* buffer = malloc(sizeof(Py_buffer));
103+
if (PyObject_GetBuffer(v, buffer, PyBUF_FULL_RO) < 0) {
66104
return NULL;
67105
}
68-
return PyMemoryView_FromBuffer(&buffer);
106+
Py_ssize_t ndim = buffer->ndim;
107+
int needs_release = 0;
108+
if (buffer->obj != NULL) {
109+
PyBufferProcs *pb;
110+
pb = Py_TYPE(buffer->obj)->tp_as_buffer;
111+
if (pb) {
112+
needs_release = pb->bf_releasebuffer != NULL;
113+
}
114+
}
115+
PyObject *mv = polyglot_invoke(PY_TRUFFLE_CEXT, "PyTruffle_MemoryViewFromBuffer",
116+
needs_release ? buffer : NULL, /* We only need the ptr for the release */
117+
native_to_java(buffer->obj),
118+
buffer->len,
119+
buffer->readonly,
120+
buffer->itemsize,
121+
polyglot_from_string(buffer->format ? buffer->format : "B", "ascii"),
122+
buffer->ndim,
123+
polyglot_from_i8_array(buffer->buf, buffer->len),
124+
buffer->shape ? polyglot_from_size_array(buffer->shape, ndim) : NULL,
125+
buffer->strides ? polyglot_from_size_array(buffer->strides, ndim) : NULL,
126+
buffer->suboffsets ? polyglot_from_size_array(buffer->suboffsets, ndim) : NULL);
127+
if (!needs_release) {
128+
free(buffer);
129+
}
130+
return mv;
69131
}
70132

71133
PyErr_Format(PyExc_TypeError,
@@ -74,27 +136,23 @@ PyObject* PyTruffle_MemoryViewFromObject(PyObject *v) {
74136
return NULL;
75137
}
76138

77-
#if SIZEOF_SIZE_T == 8
78-
#define polyglot_from_size_array polyglot_from_i64_array
79-
#elif SIZEOF_SIZE_T == 4
80-
#define polyglot_from_size_array polyglot_from_i32_array
81-
#endif
82-
83-
84-
PyObject* PyMemoryView_FromBuffer(Py_buffer *buffer) {
85-
Py_ssize_t ndim = buffer->ndim;
86-
releasebufferproc releasefn = NULL;
139+
/* Release buffer struct allocated in PyTruffle_MemoryViewFromObject */
140+
void PyTruffle_ReleaseBuffer(Py_buffer* buffer) {
87141
if (buffer->obj != NULL) {
88142
PyBufferProcs *pb;
89143
pb = Py_TYPE(buffer->obj)->tp_as_buffer;
90144
if (pb) {
91-
releasefn = pb->bf_releasebuffer;
145+
pb->bf_releasebuffer(buffer->obj, buffer);
92146
}
93147
}
148+
free(buffer);
149+
}
150+
151+
PyObject* PyMemoryView_FromBuffer(Py_buffer *buffer) {
152+
Py_ssize_t ndim = buffer->ndim;
94153
return polyglot_invoke(PY_TRUFFLE_CEXT, "PyTruffle_MemoryViewFromBuffer",
95-
buffer,
96-
native_to_java(buffer->obj),
97-
releasefn,
154+
NULL,
155+
NULL,
98156
buffer->len,
99157
buffer->readonly,
100158
buffer->itemsize,
@@ -111,47 +169,14 @@ PyObject *PyMemoryView_FromMemory(char *mem, Py_ssize_t size, int flags) {
111169
assert(flags == PyBUF_READ || flags == PyBUF_WRITE);
112170
int readonly = (flags == PyBUF_WRITE) ? 0 : 1;
113171
return polyglot_invoke(PY_TRUFFLE_CEXT, "PyTruffle_MemoryViewFromBuffer",
114-
NULL, NULL, NULL, size, readonly, 1, polyglot_from_string("B", "ascii"), 1, polyglot_from_i8_array((int8_t*)mem, size), NULL, NULL, NULL);
172+
NULL, NULL, size, readonly, 1, polyglot_from_string("B", "ascii"), 1, polyglot_from_i8_array((int8_t*)mem, size), NULL, NULL, NULL);
115173
}
116174

117175
UPCALL_ID(PyMemoryView_GetContiguous)
118176
PyObject* PyMemoryView_GetContiguous(PyObject *obj, int buffertype, char order) {
119177
return UPCALL_CEXT_O(_jls_PyMemoryView_GetContiguous, native_to_java(obj), buffertype, (int)order);
120178
}
121179

122-
/* Macros taken from CPython */
123-
/* Memoryview buffer properties */
124-
#define MV_C_CONTIGUOUS(flags) (flags&(_Py_MEMORYVIEW_SCALAR|_Py_MEMORYVIEW_C))
125-
#define MV_F_CONTIGUOUS(flags) \
126-
(flags&(_Py_MEMORYVIEW_SCALAR|_Py_MEMORYVIEW_FORTRAN))
127-
#define MV_ANY_CONTIGUOUS(flags) \
128-
(flags&(_Py_MEMORYVIEW_SCALAR|_Py_MEMORYVIEW_C|_Py_MEMORYVIEW_FORTRAN))
129-
130-
/* getbuffer() requests */
131-
#define REQ_INDIRECT(flags) ((flags&PyBUF_INDIRECT) == PyBUF_INDIRECT)
132-
#define REQ_C_CONTIGUOUS(flags) ((flags&PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS)
133-
#define REQ_F_CONTIGUOUS(flags) ((flags&PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS)
134-
#define REQ_ANY_CONTIGUOUS(flags) ((flags&PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS)
135-
#define REQ_STRIDES(flags) ((flags&PyBUF_STRIDES) == PyBUF_STRIDES)
136-
#define REQ_SHAPE(flags) ((flags&PyBUF_ND) == PyBUF_ND)
137-
#define REQ_WRITABLE(flags) (flags&PyBUF_WRITABLE)
138-
#define REQ_FORMAT(flags) (flags&PyBUF_FORMAT)
139-
140-
#define BASE_INACCESSIBLE(mv) \
141-
(((PyMemoryViewObject *)mv)->flags&_Py_MEMORYVIEW_RELEASED)
142-
#define CHECK_RELEASED(mv) \
143-
if (BASE_INACCESSIBLE(mv)) { \
144-
PyErr_SetString(PyExc_ValueError, \
145-
"operation forbidden on released memoryview object"); \
146-
return NULL; \
147-
}
148-
#define CHECK_RELEASED_INT(mv) \
149-
if (BASE_INACCESSIBLE(mv)) { \
150-
PyErr_SetString(PyExc_ValueError, \
151-
"operation forbidden on released memoryview object"); \
152-
return -1; \
153-
}
154-
155180
/* Taken from CPython memoryobject.c: memory_getbuf */
156181
int memoryview_getbuffer(PyMemoryViewObject *self, Py_buffer *view, int flags)
157182
{

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinConstructors.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3377,7 +3377,7 @@ private static IntrinsifiedPMemoryView fromManaged(Object object, int itemsize,
33773377
ManagedBuffer managedBuffer = null;
33783378
if (needsRelease) {
33793379
// TODO We should lock the underlying storage for resizing
3380-
managedBuffer = new ManagedBuffer(object);
3380+
managedBuffer = ManagedBuffer.createForManaged(object);
33813381
}
33823382
return new IntrinsifiedPMemoryView(PythonBuiltinClassType.PMemoryView, PythonBuiltinClassType.PMemoryView.getInstanceShape(),
33833383
refQueue, managedBuffer, object, length * itemsize, readonly, itemsize, format, 1,

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PythonCextBuiltins.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1563,12 +1563,12 @@ Object wrap(VirtualFrame frame, Object object,
15631563
}
15641564

15651565
// Called without landing node
1566-
@Builtin(name = NativeCAPISymbols.FUN_PY_TRUFFLE_MEMORYVIEW_FROM_BUFFER, minNumOfPositionalArgs = 12)
1566+
@Builtin(name = NativeCAPISymbols.FUN_PY_TRUFFLE_MEMORYVIEW_FROM_BUFFER, minNumOfPositionalArgs = 11)
15671567
@GenerateNodeFactory
15681568
abstract static class PyTruffle_MemoryViewFromBuffer extends NativeBuiltin {
15691569

15701570
@Specialization
1571-
Object wrap(VirtualFrame frame, Object bufferStructPointer, Object ownerObj, Object releasefn, Object lenObj,
1571+
Object wrap(VirtualFrame frame, Object bufferStructPointer, Object ownerObj, Object lenObj,
15721572
Object readonlyObj, Object itemsizeObj, Object formatObj,
15731573
Object ndimObj, Object bufPointer, Object shapePointer, Object stridesPointer, Object suboffsetsPointer,
15741574
@Cached MemoryViewNodes.InitFlagsNode initFlagsNode,
@@ -1618,8 +1618,8 @@ Object wrap(VirtualFrame frame, Object bufferStructPointer, Object ownerObj, Obj
16181618
int flags = initFlagsNode.execute(ndim, itemsize, shape, strides, suboffsets);
16191619
// TODO factory
16201620
ManagedBuffer managedBuffer = null;
1621-
if (!lib.isNull(releasefn)) {
1622-
managedBuffer = new ManagedBuffer(owner, bufferStructPointer, releasefn);
1621+
if (!lib.isNull(bufferStructPointer)) {
1622+
managedBuffer = ManagedBuffer.createForNative(bufferStructPointer);
16231623
}
16241624
IntrinsifiedPMemoryView memoryview = new IntrinsifiedPMemoryView(PythonBuiltinClassType.PMemoryView, PythonBuiltinClassType.PMemoryView.getInstanceShape(),
16251625
getQueue.execute(), managedBuffer, owner, len, readonly, itemsize, format, ndim, bufPointer, 0, shape, strides, suboffsets, flags);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/NativeCAPISymbols.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ public abstract class NativeCAPISymbols {
109109
public static final String FUN_TRUFFLE_ADD_SUBOFFSET = "truffle_add_suboffset";
110110
public static final String FUN_PY_TRUFFLE_MEMORYVIEW_FROM_BUFFER = "PyTruffle_MemoryViewFromBuffer";
111111
public static final String FUN_PY_TRUFFLE_MEMORYVIEW_FROM_OBJECT = "PyTruffle_MemoryViewFromObject";
112+
public static final String FUN_PY_TRUFFLE_RELEASE_BUFFER = "PyTruffle_ReleaseBuffer";
112113
private static final String FUN_GET_INT8_T_TYPEID = "get_int8_t_typeid";
113114
private static final String FUN_GET_INT16_T_TYPEID = "get_int16_t_typeid";
114115
private static final String FUN_GET_INT32_T_TYPEID = "get_int32_t_typeid";

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/ManagedBuffer.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.oracle.graal.python.builtins.objects.memoryview;
22

3+
import java.lang.ref.WeakReference;
34
import java.util.concurrent.atomic.AtomicInteger;
45

56
/**
@@ -10,39 +11,40 @@
1011
* Rough equivalent of CPython's {@code _PyManagedBuffer_Type}
1112
*/
1213
public class ManagedBuffer {
13-
// Buffer owner, never null
14-
final Object owner;
14+
// Buffer owner, null for native objects
15+
final WeakReference<Object> owner;
1516
// Pointer to native Py_buffer if any, null for managed objects
1617
final Object bufferStructPointer;
17-
// Pointer to native bf_releasebuffer C function, null for managed objects
18-
final Object releasefn;
1918

2019
final AtomicInteger exports = new AtomicInteger();
2120

22-
public ManagedBuffer(Object owner, Object bufferStructPointer, Object releasefn) {
23-
assert owner != null : "Buffers without an owner object shouldn't create a ManagedBuffer";
24-
assert releasefn == null || bufferStructPointer != null;
21+
private ManagedBuffer(WeakReference<Object> owner, Object bufferStructPointer) {
2522
this.owner = owner;
2623
this.bufferStructPointer = bufferStructPointer;
27-
this.releasefn = releasefn;
2824
}
2925

30-
public ManagedBuffer(Object owner) {
31-
this(owner, null, null);
26+
public static ManagedBuffer createForManaged(Object owner) {
27+
assert owner != null;
28+
return new ManagedBuffer(new WeakReference<>(owner), null);
29+
}
30+
31+
public static ManagedBuffer createForNative(Object bufferStructPointer) {
32+
assert bufferStructPointer != null;
33+
return new ManagedBuffer(null, bufferStructPointer);
34+
}
35+
36+
public boolean isForNative() {
37+
return bufferStructPointer != null;
3238
}
3339

3440
public Object getOwner() {
35-
return owner;
41+
return owner.get();
3642
}
3743

3844
public Object getBufferStructPointer() {
3945
return bufferStructPointer;
4046
}
4147

42-
public Object getReleaseFunction() {
43-
return releasefn;
44-
}
45-
4648
public AtomicInteger getExports() {
4749
return exports;
4850
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/MemoryViewBuiltins.java

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@
6868
import com.oracle.truffle.api.dsl.NodeFactory;
6969
import com.oracle.truffle.api.dsl.Specialization;
7070
import com.oracle.truffle.api.frame.VirtualFrame;
71-
import com.oracle.truffle.api.interop.ArityException;
72-
import com.oracle.truffle.api.interop.InteropLibrary;
73-
import com.oracle.truffle.api.interop.UnsupportedMessageException;
74-
import com.oracle.truffle.api.interop.UnsupportedTypeException;
7571
import com.oracle.truffle.api.library.CachedLibrary;
7672
import com.oracle.truffle.api.object.HiddenKey;
7773
import com.oracle.truffle.api.profiles.ConditionProfile;
@@ -99,13 +95,8 @@ public void execute(PythonContext context) {
9995
}
10096
ManagedBuffer buffer = reference.getManagedBuffer();
10197
// Managed buffers should be released directly in the reference queue thread
102-
assert buffer.getBufferStructPointer() != null;
103-
assert buffer.getReleaseFunction() != null;
104-
try {
105-
InteropLibrary.getUncached().execute(buffer.getReleaseFunction(), buffer.getOwner(), buffer.getBufferStructPointer());
106-
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
107-
throw CompilerDirectives.shouldNotReachHere("Failed to invoke bf_releasebuffer", e);
108-
}
98+
assert buffer.isForNative();
99+
CExtNodes.PCallCapiFunction.getUncached().call(NativeCAPISymbols.FUN_PY_TRUFFLE_RELEASE_BUFFER, buffer.getBufferStructPointer());
109100
}
110101
}
111102

@@ -129,10 +120,15 @@ public void postInitialize(PythonCore core) {
129120
}
130121
ManagedBuffer buffer = bufferReference.getManagedBuffer();
131122
if (buffer.decrementExports() == 0) {
132-
if (buffer.getBufferStructPointer() != null) {
123+
if (buffer.isForNative()) {
133124
return new NativeBufferReleaseCallback(bufferReference);
134125
} else {
135-
MemoryViewNodes.ReleaseBufferOfManagedObjectNode.getUncached().execute(buffer.getOwner());
126+
Object owner = buffer.getOwner();
127+
// It's a weakref, it may go away and in that case we don't have to do
128+
// anything
129+
if (owner != null) {
130+
MemoryViewNodes.ReleaseBufferOfManagedObjectNode.getUncached().execute(owner);
131+
}
136132
return null;
137133
}
138134
}
@@ -719,28 +715,26 @@ Object releaseSimple(IntrinsifiedPMemoryView self) {
719715
return PNone.NONE;
720716
}
721717

722-
@Specialization(guards = {"self.getReference() != null", "self.getManagedBuffer().getBufferStructPointer() == null"})
718+
@Specialization(guards = {"self.getReference() != null", "!self.getManagedBuffer().isForNative()"})
723719
Object releaseManaged(IntrinsifiedPMemoryView self,
724720
@Cached MemoryViewNodes.ReleaseBufferOfManagedObjectNode release) {
725721
checkExports(self);
726722
if (checkShouldReleaseBuffer(self)) {
727-
release.execute(self.getManagedBuffer().getOwner());
723+
release.execute(self.getOwner());
728724
}
729725
self.setReleased();
730726
return PNone.NONE;
731727
}
732728

733-
@Specialization(guards = {"self.getReference() != null", "self.getManagedBuffer().getBufferStructPointer() != null"})
729+
@Specialization(guards = {"self.getReference() != null", "self.getManagedBuffer().isForNative()"})
734730
Object releaseNative(VirtualFrame frame, IntrinsifiedPMemoryView self,
735-
@CachedLibrary(limit = "1") InteropLibrary lib) {
731+
@Cached CExtNodes.PCallCapiFunction callRelease) {
736732
checkExports(self);
737733
if (checkShouldReleaseBuffer(self)) {
738734
Object state = IndirectCallContext.enter(frame, getContext(), this);
739735
ManagedBuffer buffer = self.getManagedBuffer();
740736
try {
741-
lib.execute(buffer.getReleaseFunction(), buffer.getOwner(), buffer.getBufferStructPointer());
742-
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
743-
throw CompilerDirectives.shouldNotReachHere("Failed to invoke bf_releasebuffer", e);
737+
callRelease.call(NativeCAPISymbols.FUN_PY_TRUFFLE_RELEASE_BUFFER, buffer.getBufferStructPointer());
744738
} finally {
745739
IndirectCallContext.exit(frame, getContext(), state);
746740
}

graalpython/lib-python/3/test/test_memoryview.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,11 @@ class MyObject:
242242
b.o = o
243243
wr = weakref.ref(o)
244244
b = m = o = None
245+
# XXX GraalVM change - add more collect calls
246+
for i in range(10):
247+
test.support.gc_collect()
248+
time.sleep(0.1)
245249
# The cycle must be broken
246-
gc.collect()
247250
self.assertTrue(wr() is None, wr())
248251

249252
# This exercises memory_clear().
@@ -253,8 +256,11 @@ class MyObject:
253256
m.o = o
254257
wr = weakref.ref(o)
255258
m = o = None
259+
# XXX GraalVM change - add more collect calls
260+
for i in range(10):
261+
test.support.gc_collect()
262+
time.sleep(0.1)
256263
# The cycle must be broken
257-
gc.collect()
258264
self.assertTrue(wr() is None, wr())
259265

260266
def _check_released(self, m, tp):
@@ -356,7 +362,7 @@ def callback(wr, b=b):
356362
self.assertIs(wr(), m)
357363
del m
358364
# XXX GraalVM change - add collect call
359-
for i in range(5):
365+
for i in range(10):
360366
test.support.gc_collect()
361367
time.sleep(0.1)
362368
self.assertIs(wr(), None)

0 commit comments

Comments
 (0)