execute method

Future<void> execute({
  1. Duration duration = const Duration(seconds: 5),
})

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();
  }
}