Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ffi_init_once thread-safe in free threaded build #143

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/c/_cffi_backend.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,27 @@
# define USE_WRITEUNRAISABLEMSG
#endif

#if PY_VERSION_HEX <= 0x030d00a1
static int PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result)
{
PyObject *obj = PyDict_GetItemWithError(mp, key);
Py_XINCREF(obj);
*result = obj;
if (obj == NULL) {
if (PyErr_Occurred()) {
return -1;
}
return 0;
}
return 1;
}
#endif

#if PY_VERSION_HEX <= 0x030d00b3
# define Py_BEGIN_CRITICAL_SECTION(op) {
# define Py_END_CRITICAL_SECTION() }
#endif

/************************************************************/

/* base type flag: exactly one of the following: */
Expand Down
21 changes: 13 additions & 8 deletions src/c/ffi_obj.c
Original file line number Diff line number Diff line change
Expand Up @@ -1001,16 +1001,21 @@ static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds)
in this function */

/* atomically get or create a new dict (no GIL release) */
Py_BEGIN_CRITICAL_SECTION(self);
cache = self->init_once_cache;
if (cache == NULL) {
cache = PyDict_New();
if (cache == NULL)
return NULL;
self->init_once_cache = cache;
self->init_once_cache = cache = PyDict_New();
}
Py_END_CRITICAL_SECTION();

if (cache == NULL) {
return NULL;
}

/* get the tuple from cache[tag], or make a new one: (False, lock) */
tup = PyDict_GetItem(cache, tag);
if (PyDict_GetItemRef(cache, tag, &tup) < 0) {
return NULL;
}
if (tup == NULL) {
lock = PyThread_allocate_lock();
if (lock == NULL)
Expand All @@ -1033,17 +1038,17 @@ static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds)
Py_DECREF(x);
if (tup == NULL)
return NULL;

Py_DECREF(tup); /* there is still a ref inside the dict */
}

res = PyTuple_GET_ITEM(tup, 1);
Py_INCREF(res);

if (PyTuple_GET_ITEM(tup, 0) == Py_True) {
/* tup == (True, result): return the result. */
Py_DECREF(tup);
return res;
}
Py_DECREF(tup);

/* tup == (False, lock) */
lockobj = res;
Expand All @@ -1058,7 +1063,7 @@ static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds)
PyThread_acquire_lock(lock, WAIT_LOCK);
Py_END_ALLOW_THREADS

x = PyDict_GetItem(cache, tag);
x = PyDict_GetItem(cache, tag); /* protected by lock */
if (x != NULL && PyTuple_GET_ITEM(x, 0) == Py_True) {
/* the real result was put in the dict while we were waiting
for PyThread_acquire_lock() above */
Expand Down