mirror of
https://github.com/ente-io/ente.git
synced 2025-05-28 21:47:57 +00:00
171 lines
5.4 KiB
Dart
171 lines
5.4 KiB
Dart
import 'dart:async';
|
|
import 'dart:isolate';
|
|
|
|
import "package:dart_ui_isolate/dart_ui_isolate.dart";
|
|
import "package:flutter/foundation.dart" show kDebugMode;
|
|
import "package:logging/logging.dart";
|
|
import "package:photos/core/error-reporting/isolate_logging.dart";
|
|
import "package:photos/models/base/id.dart";
|
|
import "package:photos/services/isolate_functions.dart";
|
|
import "package:synchronized/synchronized.dart";
|
|
|
|
abstract class SuperIsolate {
|
|
Logger get logger;
|
|
|
|
Timer? _inactivityTimer;
|
|
final Duration _inactivityDuration = const Duration(seconds: 120);
|
|
int _activeTasks = 0;
|
|
|
|
final _initIsolateLock = Lock();
|
|
final _functionLock = Lock();
|
|
|
|
bool get isDartUiIsolate;
|
|
bool get shouldAutomaticDispose;
|
|
String get isolateName;
|
|
|
|
late dynamic _isolate;
|
|
late ReceivePort _receivePort;
|
|
late SendPort _mainSendPort;
|
|
|
|
bool get isIsolateSpawned => _isIsolateSpawned;
|
|
bool _isIsolateSpawned = false;
|
|
|
|
Future<void> _initIsolate() async {
|
|
return _initIsolateLock.synchronized(() async {
|
|
if (_isIsolateSpawned) return;
|
|
|
|
_receivePort = ReceivePort();
|
|
|
|
try {
|
|
_isolate = isDartUiIsolate
|
|
? await DartUiIsolate.spawn(
|
|
_isolateMain,
|
|
_receivePort.sendPort,
|
|
)
|
|
: await Isolate.spawn(
|
|
_isolateMain,
|
|
_receivePort.sendPort,
|
|
debugName: isolateName,
|
|
);
|
|
_mainSendPort = await _receivePort.first as SendPort;
|
|
|
|
if (shouldAutomaticDispose) _resetInactivityTimer();
|
|
logger.info('initIsolate done');
|
|
_isIsolateSpawned = true;
|
|
} catch (e) {
|
|
logger.severe('Could not spawn isolate', e);
|
|
_isIsolateSpawned = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
@pragma('vm:entry-point')
|
|
static void _isolateMain(SendPort mainSendPort) async {
|
|
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
|
|
final IsolateLogger isolateLogger = IsolateLogger();
|
|
Logger.root.onRecord.listen(isolateLogger.onLogRecordInIsolate);
|
|
final receivePort = ReceivePort();
|
|
mainSendPort.send(receivePort.sendPort);
|
|
|
|
receivePort.listen((message) async {
|
|
final taskID = message[0] as String;
|
|
final functionIndex = message[1] as int;
|
|
final function = IsolateOperation.values[functionIndex];
|
|
final args = message[2] as Map<String, dynamic>;
|
|
final sendPort = message[3] as SendPort;
|
|
|
|
late final Object data;
|
|
try {
|
|
data = await isolateFunction(function, args);
|
|
} catch (e, stackTrace) {
|
|
data = {
|
|
'error': e.toString(),
|
|
'stackTrace': stackTrace.toString(),
|
|
};
|
|
}
|
|
final logs = List<String>.from(isolateLogger.getLogStringsAndClear());
|
|
sendPort.send({"taskID": taskID, "data": data, "logs": logs});
|
|
});
|
|
}
|
|
|
|
/// The common method to run any operation in the isolate.
|
|
/// It sends the [message] to [_isolateMain] and waits for the result.
|
|
/// The actual function executed is [isolateFunction].
|
|
Future<dynamic> runInIsolate(
|
|
IsolateOperation operation,
|
|
Map<String, dynamic> args,
|
|
) async {
|
|
await _initIsolate();
|
|
return _functionLock.synchronized(() async {
|
|
if (shouldAutomaticDispose) _resetInactivityTimer();
|
|
|
|
if (postFunctionlockStop(operation)) {
|
|
return null;
|
|
}
|
|
|
|
final completer = Completer<dynamic>();
|
|
final answerPort = ReceivePort();
|
|
|
|
_activeTasks++;
|
|
final taskID = newIsolateTaskID(operation.name);
|
|
_mainSendPort.send([taskID, operation.index, args, answerPort.sendPort]);
|
|
|
|
answerPort.listen((receivedMessage) {
|
|
if (receivedMessage['taskID'] != taskID) {
|
|
logger.severe("Received isolate message with wrong taskID");
|
|
return;
|
|
}
|
|
final logs = receivedMessage['logs'] as List<String>;
|
|
IsolateLogger.handLogStringsToMainLogger(logs);
|
|
final data = receivedMessage['data'];
|
|
if (data is Map && data.containsKey('error')) {
|
|
// Handle the error
|
|
final errorMessage = data['error'];
|
|
final errorStackTrace = data['stackTrace'];
|
|
final exception = Exception(errorMessage);
|
|
final stackTrace = StackTrace.fromString(errorStackTrace);
|
|
completer.completeError(exception, stackTrace);
|
|
} else {
|
|
completer.complete(data);
|
|
}
|
|
});
|
|
_activeTasks--;
|
|
|
|
return completer.future;
|
|
});
|
|
}
|
|
|
|
bool postFunctionlockStop(IsolateOperation operation) => false;
|
|
|
|
/// Resets a timer that kills the isolate after a certain amount of inactivity.
|
|
///
|
|
/// Should be called after initialization (e.g. inside `init()`) and after every call to isolate (e.g. inside `_runInIsolate()`)
|
|
void _resetInactivityTimer() {
|
|
_inactivityTimer?.cancel();
|
|
_inactivityTimer = Timer(_inactivityDuration, () {
|
|
if (_activeTasks > 0) {
|
|
logger.info('Tasks are still running. Delaying isolate disposal.');
|
|
// Optionally, reschedule the timer to check again later.
|
|
_resetInactivityTimer();
|
|
} else {
|
|
logger.info(
|
|
'Isolate has been inactive for ${_inactivityDuration.inSeconds} seconds with no tasks running. Killing isolate.',
|
|
);
|
|
_disposeIsolate();
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> onDispose() async {}
|
|
|
|
void _disposeIsolate() async {
|
|
if (!_isIsolateSpawned) return;
|
|
logger.info('Disposing isolate');
|
|
await onDispose();
|
|
_isIsolateSpawned = false;
|
|
_isolate.kill();
|
|
_receivePort.close();
|
|
_inactivityTimer?.cancel();
|
|
}
|
|
}
|