Skip to content

Commit 9391c4c

Browse files
committed
Manage Python GIL state for thread safety
Introduces _mainThreadState to save and restore the Python GIL using PyEval_SaveThread and PyEval_RestoreThread, ensuring thread safety when running Python code in isolates. This prevents crashes on repeated interpreter initialization and allows other threads to acquire the GIL as needed.
1 parent 8fecd92 commit 9391c4c

File tree

1 file changed

+33
-16
lines changed

1 file changed

+33
-16
lines changed

src/serious_python_android/lib/src/cpython.dart

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CPython? _cpython;
1414
// Keep a single interpreter per process; repeated init/finalize of CPython 3.12
1515
// from an embedder is fragile and was crashing on second launch.
1616
bool _pythonInitialized = false;
17+
Pointer<PyThreadState>? _mainThreadState;
1718

1819
CPython getCPython(String dynamicLibPath) {
1920
return _cpython ??= _cpython = CPython(DynamicLibrary.open(dynamicLibPath));
@@ -56,6 +57,8 @@ Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
5657
final cpython = getCPython(dynamicLibPath);
5758
if (!_pythonInitialized) {
5859
cpython.Py_Initialize();
60+
// Release the GIL from the init thread so other threads can reacquire it.
61+
_mainThreadState = cpython.PyEval_SaveThread();
5962
_pythonInitialized = true;
6063
debugPrint("after Py_Initialize()");
6164
} else {
@@ -64,23 +67,37 @@ Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
6467

6568
var result = "";
6669

67-
if (script != "") {
68-
// run script
69-
final scriptPtr = script.toNativeUtf8();
70-
int sr = cpython.PyRun_SimpleString(scriptPtr.cast<Char>());
71-
debugPrint("PyRun_SimpleString for script result: $sr");
72-
malloc.free(scriptPtr);
73-
if (sr != 0) {
74-
result = getPythonError(cpython);
75-
}
76-
} else {
77-
// run program
78-
final moduleNamePtr = programModuleName.toNativeUtf8();
79-
var modulePtr = cpython.PyImport_ImportModule(moduleNamePtr.cast<Char>());
80-
if (modulePtr == nullptr) {
81-
result = getPythonError(cpython);
70+
// Reacquire the GIL using the saved main thread state for this process.
71+
final mainState = _mainThreadState;
72+
if (mainState == null) {
73+
throw StateError("Python main thread state is null after initialization");
74+
}
75+
76+
cpython.PyEval_RestoreThread(mainState);
77+
78+
try {
79+
if (script != "") {
80+
// run script
81+
final scriptPtr = script.toNativeUtf8();
82+
int sr = cpython.PyRun_SimpleString(scriptPtr.cast<Char>());
83+
debugPrint("PyRun_SimpleString for script result: $sr");
84+
malloc.free(scriptPtr);
85+
if (sr != 0) {
86+
result = getPythonError(cpython);
87+
}
88+
} else {
89+
// run program
90+
final moduleNamePtr = programModuleName.toNativeUtf8();
91+
var modulePtr =
92+
cpython.PyImport_ImportModule(moduleNamePtr.cast<Char>());
93+
if (modulePtr == nullptr) {
94+
result = getPythonError(cpython);
95+
}
96+
malloc.free(moduleNamePtr);
8297
}
83-
malloc.free(moduleNamePtr);
98+
} finally {
99+
// Drop the GIL again so subsequent runs/threads can re-acquire it cleanly.
100+
_mainThreadState = cpython.PyEval_SaveThread();
84101
}
85102

86103
// cpython.Py_Finalize();

0 commit comments

Comments
 (0)