Skip to content

Commit b9b6eca

Browse files
Prevent potential deadlock
This fixes a potential deadlock between the GIL and EPICS record lock
1 parent 906e058 commit b9b6eca

File tree

1 file changed

+26
-4
lines changed

1 file changed

+26
-4
lines changed

softioc/extension.c

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,26 @@ static PyObject *db_put_field(PyObject *self, PyObject *args)
107107
if (dbNameToAddr(name, &dbAddr))
108108
return PyErr_Format(
109109
PyExc_RuntimeError, "dbNameToAddr failed for %s", name);
110-
if (dbPutField(&dbAddr, dbrType, pbuffer, length))
110+
111+
long put_result;
112+
/* There are two important locks to consider at this point: The Global
113+
* Interpreter Lock (GIL) and the EPICS record lock. A deadlock is possible if
114+
* this thread holds the GIL and wants the record lock (which happens inside
115+
* dbPutField), and there exists another EPICS thread that has the record lock
116+
* and wants to call Python (which requires the GIL).
117+
* This can occur if this code is called as part of an asynchronous on_update
118+
* callback.
119+
* Therefore, we must ensure we relinquish the GIL while we perform this
120+
* EPICS call, to avoid potential deadlocks.
121+
* See https://github.com/dls-controls/pythonSoftIOC/issues/119. */
122+
Py_BEGIN_ALLOW_THREADS
123+
put_result = dbPutField(&dbAddr, dbrType, pbuffer, length);
124+
Py_END_ALLOW_THREADS
125+
if (put_result)
111126
return PyErr_Format(
112127
PyExc_RuntimeError, "dbPutField failed for %s", name);
113-
Py_RETURN_NONE;
128+
else
129+
Py_RETURN_NONE;
114130
}
115131

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

146+
long get_result;
130147
long options = 0;
131-
if (dbGetField(&dbAddr, dbrType, pbuffer, &options, &length, NULL))
148+
/* See reasoning for Python macros in long comment in db_put_field. */
149+
Py_BEGIN_ALLOW_THREADS
150+
get_result = dbGetField(&dbAddr, dbrType, pbuffer, &options, &length, NULL);
151+
Py_END_ALLOW_THREADS
152+
if (get_result)
132153
return PyErr_Format(
133154
PyExc_RuntimeError, "dbGetField failed for %s", name);
134-
Py_RETURN_NONE;
155+
else
156+
Py_RETURN_NONE;
135157
}
136158

137159
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

0 commit comments

Comments
 (0)