@@ -16,6 +16,7 @@ CPython? _cpython;
1616Isolate ? _pythonIsolate;
1717SendPort ? _pythonSendPort;
1818bool _signalPatched = false ;
19+ const _pythonRunTimeout = Duration (seconds: 30 );
1920
2021void _pyLog (String msg) {
2122 debugPrint ("[PY] ${DateTime .now ().toIso8601String ()} $msg " );
@@ -35,20 +36,27 @@ Future<String> runPythonProgramFFI(bool sync, String dynamicLibPath,
3536 }
3637
3738 // Ensure a single long-lived isolate/thread to keep Python "main thread" consistent.
38- if (_pythonIsolate == null || _pythonSendPort == null ) {
39- final readyPort = ReceivePort ();
40- _pythonIsolate =
41- await Isolate .spawn (_pythonIsolateMain, readyPort.sendPort);
42- _pythonSendPort = await readyPort.first as SendPort ;
43- readyPort.close ();
44- }
39+ await _ensurePythonIsolate ();
4540
4641 final response = ReceivePort ();
47- _pythonSendPort!
48- .send ([response.sendPort, dynamicLibPath, pythonProgramPath, script]);
49- final result = await response.first as String ;
50- response.close ();
51- return result;
42+ _pythonSendPort! .send (
43+ [response.sendPort, dynamicLibPath, pythonProgramPath, script]);
44+ try {
45+ final result = await response.first
46+ .timeout (_pythonRunTimeout, onTimeout: () => "<timeout>" );
47+ response.close ();
48+ if (result == "<timeout>" ) {
49+ _pyLog ("Python run timed out; restarting interpreter isolate" );
50+ _restartPythonIsolate ();
51+ return "Python run timed out" ;
52+ }
53+ return result as String ;
54+ } catch (e) {
55+ response.close ();
56+ _pyLog ("Python run failed: $e " );
57+ _restartPythonIsolate ();
58+ rethrow ;
59+ }
5260}
5361
5462// Long-lived isolate entry point to serialize all Python runs on one thread.
@@ -61,13 +69,37 @@ void _pythonIsolateMain(SendPort readyPort) {
6169 final dynamicLibPath = args[1 ] as String ;
6270 final pythonProgramPath = args[2 ] as String ;
6371 final script = args[3 ] as String ;
64- final result = await runPythonProgramInIsolate (
65- [sendPort, dynamicLibPath, pythonProgramPath, script]);
66- // runPythonProgramInIsolate already sends the result, but return anyway.
67- sendPort.send (result);
72+ try {
73+ final result = await runPythonProgramInIsolate (
74+ [sendPort, dynamicLibPath, pythonProgramPath, script]);
75+ // runPythonProgramInIsolate already sends the result, but return anyway.
76+ sendPort.send (result);
77+ } catch (e) {
78+ sendPort.send ("Python isolate error: $e " );
79+ }
6880 });
6981}
7082
83+ Future <void > _ensurePythonIsolate () async {
84+ if (_pythonIsolate != null && _pythonSendPort != null ) {
85+ return ;
86+ }
87+ _pyLog ("spawning Python isolate" );
88+ final readyPort = ReceivePort ();
89+ _pythonIsolate = await Isolate .spawn (_pythonIsolateMain, readyPort.sendPort);
90+ _pythonSendPort = await readyPort.first as SendPort ;
91+ readyPort.close ();
92+ _signalPatched = false ;
93+ }
94+
95+ void _restartPythonIsolate () {
96+ _pyLog ("restarting Python isolate" );
97+ _pythonIsolate? .kill (priority: Isolate .immediate);
98+ _pythonIsolate = null ;
99+ _pythonSendPort = null ;
100+ _signalPatched = false ;
101+ }
102+
71103Future <String > runPythonProgramInIsolate (List <Object > arguments) async {
72104 final sendPort = arguments[0 ] as SendPort ;
73105 final dynamicLibPath = arguments[1 ] as String ;
0 commit comments