execute method
Execute the DartBlockProgram.
A new Isolate is spawned, on which the execution of the DartBlockProgram will occur.
This ensures that the UI is not frozen during the execution.
Additionally, a timer is applied. If the execution has not terminated after a set Duration, it is automatically interrupted by killing the Isolate and throwing a DartBlockException indicating that the user's program may contain an infinite loop or a faulty recursive function without a proper ending condition.
By default, the Duration is 5s, which is also the minimum duration allowed.
IMPORTANT: as Dart does not support Isolates on web platforms, the execution model is much more rudimentary:
- The DartBlockProgram is executed on the main thread, meaning the UI will freeze until the execution has finished.
- No timer is applied, meaning a faulty program whose execution never ends will lead to an indefinite stall of the app.
More info on the web platform and isolates: https://docs.flutter.dev/perf/isolates#web-platforms-and-compute
Implementation
Future<void> execute({Duration duration = const Duration(seconds: 5)}) async {
if (duration.inSeconds < 5) {
duration = Duration(seconds: 5);
}
// Reset the environment to wipe traces of the previous execution.
_reset();
if (kIsWeb) {
_executeWeb();
return;
}
final resultPort = ReceivePort();
final errorPort = ReceivePort();
final exitPort = ReceivePort();
Isolate? isolate;
Timer? timeoutTimer;
StreamSubscription? resultSub;
StreamSubscription? errorSub;
StreamSubscription? exitSub;
final completer = Completer<Map<String, dynamic>?>();
try {
// serializable payload
final Map<String, dynamic> payload = {'program': program.toJson()};
isolate = await Isolate.spawn<_IsolateArgs>(
_isolateEntry, // top-level function
_IsolateArgs(resultPort.sendPort, payload),
paused: true,
onError: errorPort.sendPort,
onExit: exitPort.sendPort,
);
// Listen for the worker result
resultSub = resultPort.listen((message) {
if (!completer.isCompleted) {
completer.complete(message as Map<String, dynamic>?);
}
});
// Listen for uncaught errors from the isolate runtime
errorSub = errorPort.listen((message) {
if (!completer.isCompleted) {
completer.completeError(message);
}
});
// If isolate exits without sending a payload
exitSub = exitPort.listen((_) {
if (!completer.isCompleted) completer.complete(null);
});
// Start the timeout timer that kills the isolate after the given duration.
timeoutTimer = Timer(duration, () {
if (!completer.isCompleted) {
try {
isolate?.kill(priority: Isolate.immediate);
} catch (_) {}
completer.complete(null);
}
});
// Resume (start) isolate and wait for completion/timeout/error
isolate.resume(isolate.pauseCapability!);
final responseMap = await completer.future;
// Cleanup listeners + ports + timer
await resultSub.cancel();
await errorSub.cancel();
await exitSub.cancel();
resultPort.close();
errorPort.close();
exitPort.close();
timeoutTimer.cancel();
// Handle the resultMap (null => timeout/killed)
if (responseMap == null) {
// treat as timeout
_thrownException = DartBlockException(
title: "Infinite Loop",
message:
"The program was killed due to timeout. Ensure your program does not contain an infinite loop or a recursive function without an ending condition!",
);
printToConsole("Program execution interrupted by exception.");
return;
}
final response = DartBlockExecutionResult.fromJson(responseMap);
// Apply state of isolate's executor to this executor
_consoleOutput
..clear()
..addAll(response.consoleOutput);
final env = response.getEnvironment();
if (env != null) {
environment.copyFrom(env); // deep copy
}
_currentStatementBlockKey = response.currentStatementBlockKey;
_currentStatement = response.getCurrentStatement();
_blockHistory
..clear()
..addAll(response.blockHistory);
_thrownException = response.getException();
if (_thrownException != null) {
printToConsole("Program execution interrupted by exception.");
} else {
printToConsole("Program execution finished successfully.");
}
} catch (ex) {
// Should normally never occur.
_thrownException = DartBlockException(
title: "Critical Error",
message:
"The program was killed due to an unknown error. Ensure your program does not contain an infinite loop or a recursive function without an ending condition!",
);
printToConsole("Program execution interrupted by exception.");
} finally {
// clean-up
try {
await resultSub?.cancel();
await errorSub?.cancel();
await exitSub?.cancel();
} catch (_) {}
resultPort.close();
errorPort.close();
exitPort.close();
timeoutTimer?.cancel();
}
}