Skip to content

Commit b56a2ea

Browse files
authored
Merge pull request #564 from lf-lang/python-allow-async-threads-concurrently
Python target: Allow the execution of async Python threads concurrently with the C runtime
2 parents d6f3357 + b331603 commit b56a2ea

File tree

1 file changed

+29
-20
lines changed

1 file changed

+29
-20
lines changed

org.lflang/src/lib/Python/pythontarget.c

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,15 @@ static PyObject* py_request_stop(PyObject *self) {
254254
static PyObject* py_main(PyObject *self, PyObject *args) {
255255
DEBUG_PRINT("Initializing main.");
256256
const char *argv[] = {TOSTRING(MODULE_NAME), NULL };
257+
258+
// Initialize the Python interpreter
259+
Py_Initialize();
260+
261+
DEBUG_PRINT("Initialized the Python interpreter.");
262+
263+
Py_BEGIN_ALLOW_THREADS
257264
lf_reactor_c_main(1, argv);
265+
Py_END_ALLOW_THREADS
258266

259267
Py_INCREF(Py_None);
260268
return Py_None;
@@ -966,25 +974,22 @@ PyObject* convert_C_action_to_py(void* action) {
966974
*/
967975
PyObject*
968976
get_python_function(string module, string class, int instance_id, string func) {
969-
970-
// Set if the interpreter is already initialized
971-
int is_initialized = 0;
972-
973-
if (Py_IsInitialized()) {
974-
is_initialized = 1;
975-
}
976-
977977
DEBUG_PRINT("Starting the function start().");
978978

979979
// Necessary PyObject variables to load the react() function from test.py
980980
PyObject *pFileName, *pModule, *pDict, *pClasses, *pClass, *pFunc;
981981

982982
PyObject *rValue;
983983

984-
// Initialize the Python interpreter
985-
Py_Initialize();
986-
987-
DEBUG_PRINT("Initialized the Python interpreter.");
984+
// According to
985+
// https://docs.python.org/3/c-api/init.html#non-python-created-threads
986+
// the following code does the following:
987+
// - Register this thread with the interpreter
988+
// - Acquire the GIL (Global Interpreter Lock)
989+
// - Store (return) the thread pointer
990+
// When done, we should always call PyGILState_Release(gstate);
991+
PyGILState_STATE gstate;
992+
gstate = PyGILState_Ensure();
988993

989994
// If the Python module is already loaded, skip this.
990995
if (globalPythonModule == NULL) {
@@ -993,8 +998,7 @@ get_python_function(string module, string class, int instance_id, string func) {
993998

994999
// Set the Python search path to be the current working directory
9951000
char cwd[PATH_MAX];
996-
if ( getcwd(cwd, sizeof(cwd)) == NULL)
997-
{
1001+
if ( getcwd(cwd, sizeof(cwd)) == NULL) {
9981002
error_print_and_exit("Failed to get the current working directory.");
9991003
}
10001004

@@ -1004,7 +1008,7 @@ get_python_function(string module, string class, int instance_id, string func) {
10041008

10051009
Py_SetPath(wcwd);
10061010

1007-
DEBUG_PRINT("Loading module %s in %s.", module, cwd);
1011+
DEBUG_PRINT("Loading module %s in %s.", module, cwd);
10081012

10091013
pModule = PyImport_Import(pFileName);
10101014

@@ -1020,6 +1024,8 @@ get_python_function(string module, string class, int instance_id, string func) {
10201024
if (pDict == NULL) {
10211025
PyErr_Print();
10221026
error_print("Failed to load contents of module %s.", module);
1027+
/* Release the thread. No Python API allowed beyond this point. */
1028+
PyGILState_Release(gstate);
10231029
return 1;
10241030
}
10251031

@@ -1042,6 +1048,8 @@ get_python_function(string module, string class, int instance_id, string func) {
10421048
if (pClasses == NULL){
10431049
PyErr_Print();
10441050
error_print("Failed to load class list \"%s\" in module %s.", class, module);
1051+
/* Release the thread. No Python API allowed beyond this point. */
1052+
PyGILState_Release(gstate);
10451053
return 1;
10461054
}
10471055

@@ -1051,6 +1059,8 @@ get_python_function(string module, string class, int instance_id, string func) {
10511059
if (pClass == NULL) {
10521060
PyErr_Print();
10531061
error_print("Failed to load class \"%s[%d]\" in module %s.", class, instance_id, module);
1062+
/* Release the thread. No Python API allowed beyond this point. */
1063+
PyGILState_Release(gstate);
10541064
return 1;
10551065
}
10561066

@@ -1066,6 +1076,8 @@ get_python_function(string module, string class, int instance_id, string func) {
10661076
if (pFunc && PyCallable_Check(pFunc)) {
10671077
DEBUG_PRINT("Calling function %s from class %s[%d].", func , class, instance_id);
10681078
Py_INCREF(pFunc);
1079+
/* Release the thread. No Python API allowed beyond this point. */
1080+
PyGILState_Release(gstate);
10691081
return pFunc;
10701082
}
10711083
else {
@@ -1086,11 +1098,8 @@ get_python_function(string module, string class, int instance_id, string func) {
10861098

10871099
DEBUG_PRINT("Done with start().");
10881100

1089-
if (is_initialized == 0) {
1090-
/* We are the first to initilize the Pyton interpreter. Destroy it when done. */
1091-
Py_FinalizeEx();
1092-
}
1093-
10941101
Py_INCREF(Py_None);
1102+
/* Release the thread. No Python API allowed beyond this point. */
1103+
PyGILState_Release(gstate);
10951104
return Py_None;
10961105
}

0 commit comments

Comments
 (0)