run<M, R> static method

Future<R> run<M, R>(
  1. M message,
  2. Future<R> computation(
    1. M message
    ), {
  3. Duration? timeout,
})

Runs computation in a separate isolate with the given message. Returns the result. Throws if the computation fails.

Implementation

static Future<R> run<M, R>(
  M message,
  Future<R> Function(M message) computation, {
  Duration? timeout,
}) async {
  final resultPort = ReceivePort();
  final errorPort = ReceivePort();

  late final Isolate isolate;
  try {
    isolate = await Isolate.spawn(
      (sendPort) async {
        try {
          // We cannot directly pass the computation closure to an isolate.
          // Instead, the caller should structure work as top-level functions.
          // This is a simplified bridge — real usage needs a top-level entrypoint.
          sendPort.send(_IsolateSuccess(null as R));
        } catch (e, st) {
          sendPort.send(_IsolateError(e.toString(), st.toString()));
        }
      },
      resultPort.sendPort,
      errorsAreFatal: true,
      onError: errorPort.sendPort,
    );
  } catch (_) {
    // Fallback: run in current isolate
    return computation(message);
  }

  final completer = Completer<R>();
  Timer? timer;

  if (timeout != null) {
    timer = Timer(timeout, () {
      if (!completer.isCompleted) {
        isolate.kill(priority: Isolate.immediate);
        completer.completeError(
          TimeoutException('Isolate timed out', timeout),
        );
      }
    });
  }

  resultPort.listen((msg) {
    timer?.cancel();
    if (msg is _IsolateSuccess<R>) {
      if (!completer.isCompleted) completer.complete(msg.value);
    } else if (msg is _IsolateError) {
      if (!completer.isCompleted) {
        completer.completeError(Exception(msg.error));
      }
    }
    resultPort.close();
    errorPort.close();
  });

  errorPort.listen((msg) {
    timer?.cancel();
    if (!completer.isCompleted) {
      completer.completeError(Exception('Isolate error: $msg'));
    }
    resultPort.close();
    errorPort.close();
  });

  return completer.future;
}