Bugfix for transferring malloc'ed data to a pyarray under windows with the capsule

This commit is contained in:
Anne de Jong 2021-06-03 21:35:04 +02:00
parent 32e5860352
commit 3f32176804
2 changed files with 64 additions and 48 deletions

View File

@ -22,65 +22,81 @@
* Function passed to Python to use for cleanup of * Function passed to Python to use for cleanup of
* foreignly obtained data. * foreignly obtained data.
**/ **/
#define LASP_CAPSULE_NAME "pyarray_data_destructor"
static inline void capsule_cleanup(PyObject *capsule) { static inline void capsule_cleanup(PyObject *capsule) {
void *memory = PyCapsule_GetPointer(capsule, NULL); void *memory = PyCapsule_GetPointer(capsule, LASP_CAPSULE_NAME);
free(memory); free(memory);
} }
#endif #endif
static inline PyObject *data_to_ndarray(void *data, int n_rows, int n_cols, static inline PyObject *data_to_ndarray(void *data, int n_rows, int n_cols,
int typenum, bool transfer_ownership, int typenum, bool transfer_ownership,
bool F_contiguous) { bool F_contiguous) {
/* fprintf(stderr, "Enter data_to_ndarray\n"); */ /* fprintf(stderr, "Enter data_to_ndarray\n"); */
assert(data); assert(data);
import_array(); import_array();
PyArray_Descr *descr = PyArray_DescrFromType(typenum);
if(!descr) return NULL;
npy_intp dims[2]; npy_intp dims[2] = {n_rows, n_cols};
if(F_contiguous){ npy_intp strides[2];
dims[0] = n_cols;
dims[1] = n_rows;
} else { int flags = 0;
dims[0] = n_rows; if(F_contiguous){
dims[1] = n_cols; flags |= NPY_ARRAY_FARRAY;
strides[0] = descr->elsize;
strides[1] = descr->elsize*n_rows;
} else {
strides[0] = descr->elsize*n_rows;
strides[1] = descr->elsize;
}
/* assert(n_rows > 0); */
/* assert(n_cols > 0); */
PyArrayObject *arr =
(PyArrayObject *)PyArray_NewFromDescr(
&PyArray_Type,
descr, // Description
2, // nd
dims, // dimensions
strides, // strides
data, // Data pointer
flags, // Flags
NULL // obj
);
if (!arr) {
fprintf(stderr, "arr = 0!");
return NULL;
}
if (transfer_ownership == true) {
#ifdef MS_WIN64
// The default destructor of Python cannot free the data, as it is allocated
// with malloc. Therefore, with this code, we tell Numpy/Python to use
// the capsule_cleanup constructor. See:
// https://stackoverflow.com/questions/54269956/crash-of-jupyter-due-to-the-use-of-pyarray-enableflags/54278170#54278170
// Note that in general it was disadvised to build all C code with MinGW on
// Windows. We do it anyway, see if we find any problems on the way.
PyObject *capsule = PyCapsule_New(data, LASP_CAPSULE_NAME, capsule_cleanup);
int res = PyArray_SetBaseObject(arr, capsule);
if(res != 0) {
fprintf(stderr, "Failed to set base object of array!");
return NULL;
} }
/* assert(n_rows > 0); */ #endif
/* assert(n_cols > 0); */ /* fprintf(stderr, "============Ownership transfer================\n"); */
PyArray_ENABLEFLAGS(arr, NPY_OWNDATA);
}
/* fprintf(stderr, "Exit data_to_ndarray\n"); */
PyArrayObject *arr = return (PyObject *) arr;
(PyArrayObject *)PyArray_SimpleNewFromData(2, dims, typenum, data);
if (!arr) {
return NULL;
}
if (F_contiguous) {
arr = (PyArrayObject*) PyArray_Transpose(arr, NULL);
}
if (transfer_ownership == true) {
#ifdef MS_WIN64
// The default destructor of Python cannot free the data, as it is allocated
// with malloc. Therefore, with this code, we tell Numpy/Python to use
// the capsule_cleanup constructor. See:
// https://stackoverflow.com/questions/54269956/crash-of-jupyter-due-to-the-use-of-pyarray-enableflags/54278170#54278170
// Note that in general it was disadvised to build all C code with MinGW on
// Windows. We do it anyway, see if we find any problems on the way.
PyObject *capsule = PyCapsule_New(data, "data destructor", capsule_cleanup);
int res = PyArray_SetBaseObject(arr, capsule);
if(res != 0) {
fprintf(stderr, "Failed to set base object of array!");
return NULL;
}
#endif
/* fprintf(stderr, "============Ownership transfer================\n"); */
PyArray_ENABLEFLAGS(arr, NPY_OWNDATA);
}
/* fprintf(stderr, "Exit data_to_ndarray\n"); */
return (PyObject *) arr;
} }
#undef LASP_CAPSULE_NAME
#endif // LASP_PYARRAY_H #endif // LASP_PYARRAY_H
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////

View File

@ -13,7 +13,7 @@
/** /**
* Create a numpy array from an existing dmat. * Create a numpy array from an existing dmat.
* *
* @param mat dmat struccture containing array data and metadata. * @param mat dmat structure containing array data and metadata.
* @param transfer_ownership If set to true, Numpy array will be responsible * @param transfer_ownership If set to true, Numpy array will be responsible
* for freeing the data. * for freeing the data.
* *