Commit b550eccc authored by Armin Rigo's avatar Armin Rigo
Browse files

Found a simpler and more efficient way to implement any ffi.gc().

parent 88b59dfa8176
......@@ -188,11 +188,13 @@ static PyTypeObject CField_Type;
static PyTypeObject CData_Type;
static PyTypeObject CDataOwning_Type;
static PyTypeObject CDataOwningGC_Type;
static PyTypeObject CDataGCP_Type;
#define CTypeDescr_Check(ob) (Py_TYPE(ob) == &CTypeDescr_Type)
#define CData_Check(ob) (Py_TYPE(ob) == &CData_Type || \
Py_TYPE(ob) == &CDataOwning_Type || \
Py_TYPE(ob) == &CDataOwningGC_Type)
Py_TYPE(ob) == &CDataOwningGC_Type || \
Py_TYPE(ob) == &CDataGCP_Type)
#define CDataOwn_Check(ob) (Py_TYPE(ob) == &CDataOwning_Type || \
Py_TYPE(ob) == &CDataOwningGC_Type)
......@@ -234,6 +236,11 @@ typedef struct {
Py_buffer *bufferview;
} CDataObject_owngc_frombuf;
typedef struct {
CDataObject head;
PyObject *origobj, *destructor;
} CDataObject_gcp;
typedef struct {
ffi_cif cif;
/* the following information is used when doing the call:
......@@ -1625,6 +1632,35 @@ static int cdataowninggc_clear(CDataObject *cd)
return 0;
}
/* forward */
static void _my_PyErr_WriteUnraisable(char *objdescr, PyObject *obj,
char *extra_error_line);
static void cdatagcp_dealloc(CDataObject_gcp *cd)
{
PyObject *result;
PyObject *destructor = cd->destructor;
PyObject *origobj = cd->origobj;
cdata_dealloc((CDataObject *)cd);
result = PyObject_CallFunctionObjArgs(destructor, origobj, NULL);
if (result != NULL) {
Py_DECREF(result);
}
else {
_my_PyErr_WriteUnraisable("From callback for ffi.gc ", origobj, NULL);
}
Py_DECREF(destructor);
Py_DECREF(origobj);
}
static int cdatagcp_traverse(CDataObject_gcp *cd, visitproc visit, void *arg)
{
Py_VISIT(cd->destructor);
Py_VISIT(cd->origobj);
return 0;
}
static PyObject *cdata_float(CDataObject *cd); /*forward*/
static PyObject *convert_cdata_to_enum_string(CDataObject *cd, int both)
......@@ -2729,6 +2765,41 @@ static PyTypeObject CDataOwningGC_Type = {
&CDataOwning_Type, /* tp_base */
};
static PyTypeObject CDataGCP_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"_cffi_backend.CDataGCP",
sizeof(CDataObject_gcp),
0,
(destructor)cdatagcp_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES /* tp_flags */
| Py_TPFLAGS_HAVE_GC,
0, /* tp_doc */
(traverseproc)cdatagcp_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&CData_Type, /* tp_base */
};
/************************************************************/
typedef struct {
......@@ -4709,7 +4780,8 @@ static int convert_from_object_fficallback(char *result,
return convert_from_object(result, ctype, pyobj);
}
static void _my_PyErr_WriteUnraisable(PyObject *obj, char *extra_error_line)
static void _my_PyErr_WriteUnraisable(char *objdescr, PyObject *obj,
char *extra_error_line)
{
/* like PyErr_WriteUnraisable(), but write a full traceback */
PyObject *f, *t, *v, *tb;
......@@ -4726,7 +4798,7 @@ static void _my_PyErr_WriteUnraisable(PyObject *obj, char *extra_error_line)
f = PySys_GetObject("stderr");
if (f != NULL) {
if (obj != NULL) {
PyFile_WriteString("From cffi callback ", f);
PyFile_WriteString(objdescr, f);
PyFile_WriteObject(obj, f, 0);
PyFile_WriteString(":\n", f);
}
......@@ -4799,7 +4871,8 @@ static void invoke_callback(ffi_cif *cif, void *result, void **args,
}
onerror_cb = PyTuple_GET_ITEM(cb_args, 3);
if (onerror_cb == Py_None) {
_my_PyErr_WriteUnraisable(py_ob, extra_error_line);
_my_PyErr_WriteUnraisable("From cffi callback ", py_ob,
extra_error_line);
}
else {
PyObject *exc1, *val1, *tb1, *res1, *exc2, *val2, *tb2;
......@@ -4824,11 +4897,12 @@ static void invoke_callback(ffi_cif *cif, void *result, void **args,
/* double exception! print a double-traceback... */
PyErr_Fetch(&exc2, &val2, &tb2);
PyErr_Restore(exc1, val1, tb1);
_my_PyErr_WriteUnraisable(py_ob, extra_error_line);
_my_PyErr_WriteUnraisable("From cffi callback ", py_ob,
extra_error_line);
PyErr_Restore(exc2, val2, tb2);
extra_error_line = ("\nDuring the call to 'onerror', "
"another exception occurred:\n\n");
_my_PyErr_WriteUnraisable(NULL, extra_error_line);
_my_PyErr_WriteUnraisable(NULL, NULL, extra_error_line);
}
}
goto done;
......@@ -5554,6 +5628,34 @@ static PyObject *b__get_types(PyObject *self, PyObject *noarg)
(PyObject *)&CTypeDescr_Type);
}
static PyObject *b_gcp(PyObject *self, PyObject *args, PyObject *kwds)
{
CDataObject_gcp *cd;
CDataObject *origobj;
PyObject *destructor;
static char *keywords[] = {"cdata", "destructor", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O:gc", keywords,
&CData_Type, &origobj, &destructor))
return NULL;
cd = PyObject_GC_New(CDataObject_gcp, &CDataGCP_Type);
if (cd == NULL)
return NULL;
Py_INCREF(destructor);
Py_INCREF(origobj);
Py_INCREF(origobj->c_type);
cd->head.c_data = origobj->c_data;
cd->head.c_type = origobj->c_type;
cd->head.c_weakreflist = NULL;
cd->origobj = (PyObject *)origobj;
cd->destructor = destructor;
PyObject_GC_Track(cd);
return (PyObject *)cd;
}
/************************************************************/
static char _testfunc0(char a, char b)
......@@ -5859,6 +5961,7 @@ static PyMethodDef FFIBackendMethods[] = {
{"newp_handle", b_newp_handle, METH_VARARGS},
{"from_handle", b_from_handle, METH_O},
{"from_buffer", b_from_buffer, METH_VARARGS},
{"gcp", (PyCFunction)b_gcp, METH_VARARGS | METH_KEYWORDS},
#ifdef MS_WIN32
{"getwinerror", (PyCFunction)b_getwinerror, METH_VARARGS | METH_KEYWORDS},
#endif
......@@ -6096,6 +6199,8 @@ init_cffi_backend(void)
INITERROR;
if (PyType_Ready(&CDataOwningGC_Type) < 0)
INITERROR;
if (PyType_Ready(&CDataGCP_Type) < 0)
INITERROR;
if (PyType_Ready(&CDataIter_Type) < 0)
INITERROR;
if (PyType_Ready(&MiniBuffer_Type) < 0)
......
......@@ -13,7 +13,6 @@ static PyTypeObject Lib_Type; /* forward */
#include "ffi_obj.c"
#include "cglob.c"
#include "cgc.c"
#include "lib_obj.c"
#include "cdlopen.c"
......
/* translated to C from cffi/gc_weakref.py */
static PyObject *gc_wref_remove(PyObject *ffi_wref_tup, PyObject *key)
{
FFIObject *ffi;
PyObject *indexobj, *destructor, *cdata, *freelist, *result;
Py_ssize_t index;
/* here, tup is a 4-tuple (ffi, destructor, cdata, index) */
if (!PyTuple_Check(ffi_wref_tup))
goto oops; /* should never occur */
ffi = (FFIObject *)PyTuple_GET_ITEM(ffi_wref_tup, 0);
destructor = PyTuple_GET_ITEM(ffi_wref_tup, 1);
cdata = PyTuple_GET_ITEM(ffi_wref_tup, 2);
indexobj = PyTuple_GET_ITEM(ffi_wref_tup, 3);
index = PyInt_AsSsize_t(indexobj);
if (index < 0)
goto oops; /* should never occur */
/* assert gc_wrefs[index] is key */
if (PyList_GET_ITEM(ffi->gc_wrefs, index) != key)
goto oops; /* should never occur */
/* gc_wrefs[index] = freelist */
/* transfer ownership of 'freelist' to 'gc_wrefs[index]' */
freelist = ffi->gc_wrefs_freelist;
PyList_SET_ITEM(ffi->gc_wrefs, index, freelist);
/* freelist = index */
ffi->gc_wrefs_freelist = indexobj;
Py_INCREF(indexobj);
/* destructor(cdata) */
result = PyObject_CallFunctionObjArgs(destructor, cdata, NULL);
Py_DECREF(key); /* free the reference that was in 'gc_wrefs[index]' */
return result;
oops:
PyErr_SetString(PyExc_SystemError, "cgc: internal inconsistency");
/* random leaks may follow */
return NULL;
}
static PyMethodDef remove_callback = {
"gc_wref_remove", (PyCFunction)gc_wref_remove, METH_O
};
static PyObject *gc_weakrefs_build(FFIObject *ffi, CDataObject *cdata,
PyObject *destructor)
{
PyObject *new_cdata, *ref = NULL, *tup = NULL, *remove_fn = NULL;
Py_ssize_t index;
PyObject *datalist;
if (ffi->gc_wrefs == NULL) {
/* initialize */
datalist = PyList_New(0);
if (datalist == NULL)
return NULL;
ffi->gc_wrefs = datalist;
assert(ffi->gc_wrefs_freelist == NULL);
ffi->gc_wrefs_freelist = Py_None;
Py_INCREF(Py_None);
}
/* new_cdata = self.ffi.cast(typeof(cdata), cdata) */
new_cdata = do_cast(cdata->c_type, (PyObject *)cdata);
if (new_cdata == NULL)
goto error;
/* if freelist is None: */
datalist = ffi->gc_wrefs;
if (ffi->gc_wrefs_freelist == Py_None) {
/* index = len(gc_wrefs) */
index = PyList_GET_SIZE(datalist);
/* gc_wrefs.append(None) */
if (PyList_Append(datalist, Py_None) < 0)
goto error;
tup = Py_BuildValue("OOOn", ffi, destructor, cdata, index);
}
else {
/* index = freelist */
index = PyInt_AsSsize_t(ffi->gc_wrefs_freelist);
if (index < 0)
goto error; /* should not occur */
tup = PyTuple_Pack(4, ffi, destructor, cdata, ffi->gc_wrefs_freelist);
}
if (tup == NULL)
goto error;
remove_fn = PyCFunction_New(&remove_callback, tup);
if (remove_fn == NULL)
goto error;
ref = PyWeakref_NewRef(new_cdata, remove_fn);
if (ref == NULL)
goto error;
/* freelist = gc_wrefs[index] (which is None if we just did append(None)) */
/* transfer ownership of 'datalist[index]' into gc_wrefs_freelist */
Py_DECREF(ffi->gc_wrefs_freelist);
ffi->gc_wrefs_freelist = PyList_GET_ITEM(datalist, index);
/* gc_wrefs[index] = ref */
/* transfer ownership of 'ref' into 'datalist[index]' */
PyList_SET_ITEM(datalist, index, ref);
Py_DECREF(remove_fn);
Py_DECREF(tup);
return new_cdata;
error:
Py_XDECREF(new_cdata);
Py_XDECREF(ref);
Py_XDECREF(tup);
Py_XDECREF(remove_fn);
return NULL;
}
......@@ -664,21 +664,8 @@ PyDoc_STRVAR(ffi_gc_doc,
"Later, when this new cdata object is garbage-collected,\n"
"'destructor(old_cdata_object)' will be called.");
static PyObject *gc_weakrefs_build(FFIObject *ffi, CDataObject *cd,
PyObject *destructor); /* forward */
static PyObject *ffi_gc(FFIObject *self, PyObject *args, PyObject *kwds)
{
CDataObject *cd;
PyObject *destructor;
static char *keywords[] = {"cdata", "destructor", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O:gc", keywords,
&CData_Type, &cd, &destructor))
return NULL;
return gc_weakrefs_build(self, cd, destructor);
}
#define ffi_gc b_gcp /* ffi_gc() => b_gcp()
from _cffi_backend.c */
PyDoc_STRVAR(ffi_callback_doc,
"Return a callback object or a decorator making such a callback object.\n"
......
......@@ -72,7 +72,6 @@ class FFI(object):
self._cdefsources = []
self._included_ffis = []
self._windows_unicode = None
self._gcp = None
if hasattr(backend, 'set_ffi'):
backend.set_ffi(self)
for name in backend.__dict__:
......@@ -329,14 +328,13 @@ class FFI(object):
data. Later, when this new cdata object is garbage-collected,
'destructor(old_cdata_object)' will be called.
"""
if self._gcp is not None:
return self._gcp(cdata, destructor)
if hasattr(self._backend, 'FFI'):
compiled_ffi = self._backend.FFI()
self._gcp = compiled_ffi.gc
return self._gcp(cdata, destructor)
try:
gcp = self._backend.gcp
except AttributeError:
pass
else:
return gcp(cdata, destructor)
#
# the rest is for the ctypes backend only
with self._lock:
try:
gc_weakrefs = self.gc_weakrefs
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment