Skip to content

Commit

Permalink
Prevent potential deadlock
Browse files Browse the repository at this point in the history
This fixes a potential deadlock between the GIL and EPICS record lock
  • Loading branch information
AlexanderWells-diamond committed Dec 1, 2023
1 parent 906e058 commit b9b6eca
Showing 1 changed file with 26 additions and 4 deletions.
30 changes: 26 additions & 4 deletions softioc/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,26 @@ static PyObject *db_put_field(PyObject *self, PyObject *args)
if (dbNameToAddr(name, &dbAddr))
return PyErr_Format(
PyExc_RuntimeError, "dbNameToAddr failed for %s", name);
if (dbPutField(&dbAddr, dbrType, pbuffer, length))

long put_result;
/* There are two important locks to consider at this point: The Global
* Interpreter Lock (GIL) and the EPICS record lock. A deadlock is possible if
* this thread holds the GIL and wants the record lock (which happens inside
* dbPutField), and there exists another EPICS thread that has the record lock
* and wants to call Python (which requires the GIL).
* This can occur if this code is called as part of an asynchronous on_update
* callback.
* Therefore, we must ensure we relinquish the GIL while we perform this
* EPICS call, to avoid potential deadlocks.
* See https://github.com/dls-controls/pythonSoftIOC/issues/119. */
Py_BEGIN_ALLOW_THREADS
put_result = dbPutField(&dbAddr, dbrType, pbuffer, length);
Py_END_ALLOW_THREADS
if (put_result)
return PyErr_Format(
PyExc_RuntimeError, "dbPutField failed for %s", name);
Py_RETURN_NONE;
else
Py_RETURN_NONE;
}

static PyObject *db_get_field(PyObject *self, PyObject *args)
Expand All @@ -127,11 +143,17 @@ static PyObject *db_get_field(PyObject *self, PyObject *args)
return PyErr_Format(
PyExc_RuntimeError, "dbNameToAddr failed for %s", name);

long get_result;
long options = 0;
if (dbGetField(&dbAddr, dbrType, pbuffer, &options, &length, NULL))
/* See reasoning for Python macros in long comment in db_put_field. */
Py_BEGIN_ALLOW_THREADS
get_result = dbGetField(&dbAddr, dbrType, pbuffer, &options, &length, NULL);
Py_END_ALLOW_THREADS
if (get_result)
return PyErr_Format(
PyExc_RuntimeError, "dbGetField failed for %s", name);
Py_RETURN_NONE;
else
Py_RETURN_NONE;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
Expand Down

0 comments on commit b9b6eca

Please sign in to comment.