Skip to content

Commit bca7a85

Browse files
committed
Use sub-interpreter for each Python run in isolate
Refactors the Python execution logic to create a fresh sub-interpreter for each run, avoiding reuse of potentially wedged state from previous runs. Removes global signal patching in favor of patching within each sub-interpreter, and ensures the asyncio event loop is reset per run. This improves reliability when running Python code in isolates.
1 parent 729bfba commit bca7a85

File tree

1 file changed

+19
-23
lines changed

1 file changed

+19
-23
lines changed

src/serious_python_android/lib/src/cpython.dart

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ CPython? _cpython;
1515
// "main thread" identity across invocations.
1616
Isolate? _pythonIsolate;
1717
SendPort? _pythonSendPort;
18-
bool _signalPatched = false;
1918
const _pythonRunTimeout = Duration(seconds: 30);
2019

2120
void _pyLog(String msg) {
@@ -122,37 +121,33 @@ Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
122121
cpython.Py_Initialize();
123122
_pyLog("after Py_Initialize()");
124123
} else {
125-
// Interpreter was left running (likely from a previous app session). Tear
126-
// it down and start fresh to avoid reusing a wedged event loop.
127-
_pyLog("Python already initialized; finalizing stale interpreter");
128-
cpython.PyGILState_Ensure(); // ensure we own the GIL before finalizing
129-
cpython.Py_FinalizeEx();
130-
_pyLog("after Py_FinalizeEx(), reinitializing");
131-
cpython.Py_Initialize();
132-
_pyLog("after Py_Initialize() (fresh)");
124+
_pyLog("Python already initialized; reusing runtime");
133125
}
134126

135127
var result = "";
136128

137-
// Ensure the calling thread owns the GIL; this is safe across isolates.
129+
// Use a fresh sub-interpreter per run to avoid wedged state.
138130
final gilState = cpython.PyGILState_Ensure();
139131
_pyLog("PyGILState_Ensure -> $gilState");
132+
final mainState = cpython.PyThreadState_Get();
133+
_pyLog("main thread state: 0x${mainState.address.toRadixString(16)}");
134+
final subState = cpython.Py_NewInterpreter();
135+
if (subState == nullptr) {
136+
_pyLog("Py_NewInterpreter returned null");
137+
cpython.PyGILState_Release(gilState);
138+
sendPort.send("Py_NewInterpreter failed");
139+
return "Py_NewInterpreter failed";
140+
}
141+
_pyLog("created sub-interpreter: 0x${subState.address.toRadixString(16)}");
140142

141-
// Patch signal handling if we're not on the interpreter's main thread; some
142-
// libraries (e.g., asyncio) call signal.signal which would otherwise raise.
143-
if (!_signalPatched) {
144-
const patch = """
143+
// Within the sub-interpreter: patch signals and reset asyncio loop.
144+
const patchSignals = """
145145
import threading, signal
146146
if threading.current_thread() is not threading.main_thread():
147147
signal.signal = lambda *args, **kwargs: None
148148
""";
149-
_pyLog("patching signal.signal for non-main thread use");
150-
cpython.PyRun_SimpleString(patch.toNativeUtf8().cast());
151-
_signalPatched = true;
152-
}
149+
cpython.PyRun_SimpleString(patchSignals.toNativeUtf8().cast());
153150

154-
// Reset/replace asyncio loop between runs to avoid reusing a running loop from
155-
// a previous app session that could block new runs.
156151
const loopReset = """
157152
import asyncio
158153
try:
@@ -161,10 +156,9 @@ try:
161156
loop.stop()
162157
loop.close()
163158
asyncio.set_event_loop(asyncio.new_event_loop())
164-
except Exception as e:
159+
except Exception:
165160
pass
166161
""";
167-
_pyLog("resetting asyncio loop");
168162
cpython.PyRun_SimpleString(loopReset.toNativeUtf8().cast());
169163

170164
try {
@@ -192,7 +186,9 @@ except Exception as e:
192186
malloc.free(moduleNamePtr);
193187
}
194188
} finally {
195-
// Drop the GIL again so subsequent runs/threads can re-acquire it cleanly.
189+
_pyLog("ending sub-interpreter 0x${subState.address.toRadixString(16)}");
190+
cpython.Py_EndInterpreter(subState);
191+
cpython.PyThreadState_Swap(mainState);
196192
_pyLog("PyGILState_Release($gilState)");
197193
cpython.PyGILState_Release(gilState);
198194
}

0 commit comments

Comments
 (0)