blob: 49d149f9fb3fde397772d57b4e73fdb525d35cec [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:developer';
import 'dart:isolate';
import 'package:meta/meta.dart';
import 'constants.dart';
import 'isolates.dart' as isolates;
export 'isolates.dart' show ComputeCallback;
/// The dart:io implementation of [isolate.compute].
Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {
debugLabel ??= kReleaseMode ? 'compute' : callback.toString();
final Flow flow = Flow.begin();
Timeline.startSync('$debugLabel: start', flow: flow);
final RawReceivePort port = RawReceivePort();
Timeline.finishSync();
void timeEndAndCleanup() {
Timeline.startSync('$debugLabel: end', flow: Flow.end(flow.id));
port.close();
Timeline.finishSync();
}
final Completer<dynamic> completer = Completer<dynamic>();
port.handler = (dynamic msg) {
timeEndAndCleanup();
completer.complete(msg);
};
try {
await Isolate.spawn<_IsolateConfiguration<Q, R>>(
_spawn,
_IsolateConfiguration<Q, R>(
callback,
message,
port.sendPort,
debugLabel,
flow.id,
),
onExit: port.sendPort,
onError: port.sendPort,
debugName: debugLabel,
);
} on Object {
timeEndAndCleanup();
rethrow;
}
final dynamic response = await completer.future;
if(response == null) {
throw RemoteError('Isolate exited without result or error.', '');
}
assert(response is List<dynamic>);
response as List<dynamic>;
final int type = response.length;
assert(1 <= type && type <= 3);
switch (type) {
// success; see _buildSuccessResponse
case 1:
return response[0] as R;
// native error; see Isolate.addErrorListener
case 2:
await Future<Never>.error(RemoteError(
response[0] as String,
response[1] as String,
));
// caught error; see _buildErrorResponse
case 3:
default:
assert(type == 3 && response[2] == null);
await Future<Never>.error(
response[0] as Object,
response[1] as StackTrace,
);
}
}
@immutable
class _IsolateConfiguration<Q, R> {
const _IsolateConfiguration(
this.callback,
this.message,
this.resultPort,
this.debugLabel,
this.flowId,
);
final isolates.ComputeCallback<Q, R> callback;
final Q message;
final SendPort resultPort;
final String debugLabel;
final int flowId;
FutureOr<R> applyAndTime() {
return Timeline.timeSync(
debugLabel,
() => callback(message),
flow: Flow.step(flowId),
);
}
}
/// The spawn point MUST guarantee only one result event is sent through the
/// [SendPort.send] be it directly or indirectly i.e. [Isolate.exit].
///
/// In case an [Error] or [Exception] are thrown AFTER the data
/// is sent, they will NOT be handled or reported by the main [Isolate] because
/// it stops listening after the first event is received.
///
/// Also use the helpers [_buildSuccessResponse] and [_buildErrorResponse] to
/// build the response
Future<void> _spawn<Q, R>(_IsolateConfiguration<Q, R> configuration) async {
late final List<dynamic> computationResult;
try {
computationResult = _buildSuccessResponse(await configuration.applyAndTime());
} catch (e, s) {
computationResult = _buildErrorResponse(e, s);
}
Isolate.exit(configuration.resultPort, computationResult);
}
/// Wrap in [List] to ensure our expectations in the main [Isolate] are met.
///
/// We need to wrap a success result in a [List] because the user provided type
/// [R] could also be a [List]. Meaning, a check `result is R` could return true
/// for what was an error event.
List<R> _buildSuccessResponse<R>(R result) {
return List<R>.filled(1, result);
}
/// Wrap in [List] to ensure our expectations in the main isolate are met.
///
/// We wrap a caught error in a 3 element [List]. Where the last element is
/// always null. We do this so we have a way to know if an error was one we
/// caught or one thrown by the library code.
List<dynamic> _buildErrorResponse(Object error, StackTrace stack) {
return List<dynamic>.filled(3, null)
..[0] = error
..[1] = stack;
}