Reduce worker_manager memory allocation (#4435)

This commit is contained in:
Dat Dang
2026-04-09 18:26:19 +07:00
committed by GitHub
parent dd77b2d53a
commit 61530b61c6
22 changed files with 229 additions and 281 deletions
@@ -4,8 +4,6 @@ import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:model/account/personal_account.dart';
@@ -68,7 +66,6 @@ class HomeController extends ReloadableController {
@override
void onInit() {
if (PlatformInfo.isMobile) {
_initFlutterDownloader();
_registerReceivingFileSharing();
_registerDeepLinks();
}
@@ -96,14 +93,6 @@ class HomeController extends ReloadableController {
clearDataAndGoToLoginPage();
}
void _initFlutterDownloader() {
FlutterDownloader
.initialize(debug: kDebugMode)
.then((_) => FlutterDownloader.registerCallback(downloadCallback));
}
static void downloadCallback(String id, int status, int progress) {}
Future<void> _handleNavigateToScreen() async {
await Future.delayed(2.seconds);
final arguments = Get.arguments;
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
@@ -172,7 +173,7 @@ class MailboxDataSourceImpl extends MailboxDataSource {
StreamController<dartz.Either<Failure, Success>>? onProgressController,
}) {
return Future.sync(() async {
if (PlatformInfo.isWeb) {
if (PlatformInfo.isWeb || Platform.numberOfProcessors == 1) {
return await mailboxAPI.moveFolderContent(
session: session,
accountId: accountId,
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
@@ -39,9 +40,8 @@ class MailboxIsolateWorker {
final ThreadAPI _threadApi;
final EmailAPI _emailApi;
final Executor _isolateExecutor;
MailboxIsolateWorker(this._threadApi, this._emailApi, this._isolateExecutor);
MailboxIsolateWorker(this._threadApi, this._emailApi);
Future<List<EmailId>> markAsMailboxRead(
Session session,
@@ -50,8 +50,8 @@ class MailboxIsolateWorker {
int totalEmailUnread,
StreamController<Either<Failure, Success>> onProgressController
) async {
if (PlatformInfo.isWeb) {
return _handleMarkAsMailboxReadActionOnWeb(
if (PlatformInfo.isWeb || Platform.numberOfProcessors == 1) {
return await _handleMarkAsMailboxReadActionOnMainIsolate(
session,
accountId,
mailboxId,
@@ -63,108 +63,87 @@ class MailboxIsolateWorker {
throw const CanNotGetRootIsolateToken();
}
final result = await _isolateExecutor.execute(
arg1: MailboxMarkAsReadArguments(
session,
_threadApi,
_emailApi,
accountId,
mailboxId,
rootIsolateToken
),
fun1: _handleMarkAsMailboxReadAction,
notification: (value) {
if (value is List<EmailId>) {
log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: PERCENT ${value.length / totalEmailUnread}');
onProgressController.add(Right(UpdatingMarkAsMailboxReadState(
mailboxId: mailboxId,
totalUnread: totalEmailUnread,
countRead: value.length)));
}
});
return result;
final args = MailboxMarkAsReadArguments(
session,
_threadApi,
_emailApi,
accountId,
mailboxId,
rootIsolateToken,
);
return await workerManager.executeWithPort<List<EmailId>, int>(
_buildMarkAsReadClosure(args),
onMessage: (countRead) {
log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: PERCENT ${countRead / totalEmailUnread}');
onProgressController.add(Right(UpdatingMarkAsMailboxReadState(
mailboxId: mailboxId,
totalUnread: totalEmailUnread,
countRead: countRead)));
},
);
}
}
static Future<List<EmailId>> _handleMarkAsMailboxReadAction(
MailboxMarkAsReadArguments args,
TypeSendPort sendPort
MailboxMarkAsReadArguments args,
SendPort sendPort,
) async {
final rootIsolateToken = args.isolateToken;
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
await HiveCacheConfig.instance.setUp();
List<EmailId> emailIdsCompleted = List.empty(growable: true);
bool mailboxHasEmails = true;
UTCDate? lastReceivedDate;
EmailId? lastEmailId;
while (mailboxHasEmails) {
final emailResponse = await args.threadAPI
.getAllEmail(
args.session,
args.accountId,
limit: UnsignedInt(30),
filter: EmailFilterCondition(
inMailbox: args.mailboxId,
notKeyword: KeyWordIdentifier.emailSeen.value,
before: lastReceivedDate),
sort: <Comparator>{}..add(
EmailComparator(EmailComparatorProperty.receivedAt)
..setIsAscending(false)),
properties: Properties({
EmailProperty.id,
EmailProperty.keywords,
EmailProperty.receivedAt,
}))
.then((response) {
var listEmails = response.emailList;
if (listEmails != null && listEmails.isNotEmpty && lastEmailId != null) {
listEmails = listEmails
.where((email) => email.id != lastEmailId)
.toList();
}
return EmailsResponse(emailList: listEmails, state: response.state);
});
final listEmailUnread = emailResponse.emailList;
log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): listEmailUnread: ${listEmailUnread?.length}');
if (listEmailUnread == null || listEmailUnread.isEmpty) {
mailboxHasEmails = false;
} else {
lastEmailId = listEmailUnread.last.id;
lastReceivedDate = listEmailUnread.last.receivedAt;
final result = await args.emailAPI.markAsRead(
args.session,
args.accountId,
listEmailUnread.listEmailIds,
ReadActions.markAsRead);
log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): MARK_READ: ${result.emailIdsSuccess.length}');
emailIdsCompleted.addAll(result.emailIdsSuccess);
sendPort.send(emailIdsCompleted);
}
}
final emailIdsCompleted = await _executeMarkAsMailboxRead(
threadAPI: args.threadAPI,
emailAPI: args.emailAPI,
session: args.session,
accountId: args.accountId,
mailboxId: args.mailboxId,
onProgress: sendPort.send,
);
log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): TOTAL_READ: ${emailIdsCompleted.length}');
return emailIdsCompleted;
}
Future<List<EmailId>> _handleMarkAsMailboxReadActionOnWeb(
Future<List<EmailId>> _handleMarkAsMailboxReadActionOnMainIsolate(
Session session,
AccountId accountId,
MailboxId mailboxId,
int totalEmailUnread,
StreamController<Either<Failure, Success>> onProgressController
StreamController<Either<Failure, Success>> onProgressController,
) async {
final result = await _executeMarkAsMailboxRead(
threadAPI: _threadApi,
emailAPI: _emailApi,
session: session,
accountId: accountId,
mailboxId: mailboxId,
onProgress: (countRead) => onProgressController.add(Right(
UpdatingMarkAsMailboxReadState(
mailboxId: mailboxId,
totalUnread: totalEmailUnread,
countRead: countRead,
),
)),
);
log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnMainIsolate(): TOTAL_READ: ${result.length}');
return result;
}
static Future<List<EmailId>> _executeMarkAsMailboxRead({
required ThreadAPI threadAPI,
required EmailAPI emailAPI,
required Session session,
required AccountId accountId,
required MailboxId mailboxId,
required void Function(int countRead) onProgress,
}) async {
List<EmailId> emailIdsCompleted = List.empty(growable: true);
bool mailboxHasEmails = true;
UTCDate? lastReceivedDate;
EmailId? lastEmailId;
while (mailboxHasEmails) {
final emailResponse = await _threadApi
final emailResponse = await threadAPI
.getAllEmail(
session,
accountId,
@@ -192,7 +171,7 @@ class MailboxIsolateWorker {
});
final listEmailUnread = emailResponse.emailList;
log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): listEmailUnread: ${listEmailUnread?.length}');
log('MailboxIsolateWorker::_executeMarkAsMailboxRead(): listEmailUnread: ${listEmailUnread?.length}');
if (listEmailUnread == null || listEmailUnread.isEmpty) {
mailboxHasEmails = false;
@@ -200,22 +179,19 @@ class MailboxIsolateWorker {
lastEmailId = listEmailUnread.last.id;
lastReceivedDate = listEmailUnread.last.receivedAt;
final result = await _emailApi.markAsRead(
final result = await emailAPI.markAsRead(
session,
accountId,
listEmailUnread.listEmailIds,
ReadActions.markAsRead,
);
log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): MARK_READ: ${result.emailIdsSuccess.length}');
log('MailboxIsolateWorker::_executeMarkAsMailboxRead(): MARK_READ: ${result.emailIdsSuccess.length}');
emailIdsCompleted.addAll(result.emailIdsSuccess);
onProgressController.add(Right(UpdatingMarkAsMailboxReadState(
mailboxId: mailboxId,
totalUnread: totalEmailUnread,
countRead: emailIdsCompleted.length)));
onProgress(emailIdsCompleted.length);
}
}
log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): TOTAL_READ: ${emailIdsCompleted.length}');
log('MailboxIsolateWorker::_executeMarkAsMailboxRead(): TOTAL_READ: ${emailIdsCompleted.length}');
return emailIdsCompleted;
}
@@ -230,29 +206,27 @@ class MailboxIsolateWorker {
throw const CanNotGetRootIsolateToken();
}
final countEmailsCompleted = await _isolateExecutor.execute(
arg1: MoveFolderContentIsolateArguments(
session: session,
accountId: accountId,
threadAPI: _threadApi,
emailAPI: _emailApi,
currentMailboxId: request.mailboxId,
destinationMailboxId: request.destinationMailboxId,
isolateToken: rootIsolateToken,
markAsRead: request.markAsRead,
),
fun1: _moveFolderContentIsolateMethod,
notification: (value) {
if (value is int) {
log('$runtimeType::moveFolderContent(): Progress percent is ${value / request.totalEmails}');
onProgressController?.add(
Right<Failure, Success>(MoveFolderContentProgressState(
request.mailboxId,
value,
request.totalEmails,
)),
);
}
final args = MoveFolderContentIsolateArguments(
session: session,
accountId: accountId,
threadAPI: _threadApi,
emailAPI: _emailApi,
currentMailboxId: request.mailboxId,
destinationMailboxId: request.destinationMailboxId,
isolateToken: rootIsolateToken,
markAsRead: request.markAsRead,
);
final countEmailsCompleted = await workerManager.executeWithPort<int, int>(
_buildMoveFolderClosure(args),
onMessage: (value) {
log('$runtimeType::moveFolderContent(): Progress percent is ${value / request.totalEmails}');
onProgressController?.add(
Right<Failure, Success>(MoveFolderContentProgressState(
request.mailboxId,
value,
request.totalEmails,
)),
);
},
);
@@ -263,9 +237,17 @@ class MailboxIsolateWorker {
}
}
static Future<List<EmailId>> Function(SendPort) _buildMarkAsReadClosure(
MailboxMarkAsReadArguments args,
) => (sendPort) => _handleMarkAsMailboxReadAction(args, sendPort);
static Future<int> Function(SendPort) _buildMoveFolderClosure(
MoveFolderContentIsolateArguments args,
) => (sendPort) => _moveFolderContentIsolateMethod(args, sendPort);
static Future<int> _moveFolderContentIsolateMethod(
MoveFolderContentIsolateArguments args,
TypeSendPort sendPort,
SendPort sendPort,
) async {
final rootIsolateToken = args.isolateToken;
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
@@ -30,9 +31,8 @@ import 'package:worker_manager/worker_manager.dart';
class ThreadIsolateWorker {
final ThreadAPI _threadAPI;
final EmailAPI _emailAPI;
final Executor _isolateExecutor;
ThreadIsolateWorker(this._threadAPI, this._emailAPI, this._isolateExecutor);
ThreadIsolateWorker(this._threadAPI, this._emailAPI);
Future<List<EmailId>> emptyMailboxFolder(
Session session,
@@ -41,31 +41,29 @@ class ThreadIsolateWorker {
int totalEmails,
StreamController<dartz.Either<Failure, Success>> onProgressController
) async {
if (PlatformInfo.isWeb) {
return _emptyMailboxFolderOnWeb(session, accountId, mailboxId, totalEmails, onProgressController);
if (PlatformInfo.isWeb || Platform.numberOfProcessors == 1) {
return _emptyMailboxFolderOnMainIsolate(session, accountId, mailboxId, totalEmails, onProgressController);
} else {
final rootIsolateToken = RootIsolateToken.instance;
if (rootIsolateToken == null) {
throw const CanNotGetRootIsolateToken();
}
final result = await _isolateExecutor.execute(
arg1: EmptyMailboxFolderArguments(
session,
_threadAPI,
_emailAPI,
accountId,
mailboxId,
rootIsolateToken
),
fun1: _emptyMailboxFolderAction,
notification: (value) {
if (value is List<EmailId>) {
log('ThreadIsolateWorker::emptyMailboxFolder(): processed ${value.length} - totalEmails $totalEmails');
onProgressController.add(Right<Failure, Success>(EmptyingFolderState(
mailboxId, value.length, totalEmails
)));
}
final args = EmptyMailboxFolderArguments(
session,
_threadAPI,
_emailAPI,
accountId,
mailboxId,
rootIsolateToken,
);
final result = await workerManager.executeWithPort<List<EmailId>, int>(
_buildEmptyMailboxClosure(args),
onMessage: (processedCount) {
log('ThreadIsolateWorker::emptyMailboxFolder(): processed $processedCount - totalEmails $totalEmails');
onProgressController.add(Right<Failure, Success>(EmptyingFolderState(
mailboxId, processedCount, totalEmails
)));
},
);
@@ -77,9 +75,13 @@ class ThreadIsolateWorker {
}
}
static Future<List<EmailId>> Function(SendPort) _buildEmptyMailboxClosure(
EmptyMailboxFolderArguments args,
) => (sendPort) => _emptyMailboxFolderAction(args, sendPort);
static Future<List<EmailId>> _emptyMailboxFolderAction(
EmptyMailboxFolderArguments args,
TypeSendPort sendPort
SendPort sendPort,
) async {
final rootIsolateToken = args.isolateToken;
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
@@ -119,7 +121,7 @@ class ThreadIsolateWorker {
args.accountId,
newEmailList.listEmailIds);
emailListCompleted.addAll(listEmailIdDeleted.emailIdsSuccess);
sendPort.send(emailListCompleted);
sendPort.send(emailListCompleted.length);
} else {
hasEmails = false;
}
@@ -128,7 +130,7 @@ class ThreadIsolateWorker {
return emailListCompleted;
}
Future<List<EmailId>> _emptyMailboxFolderOnWeb(
Future<List<EmailId>> _emptyMailboxFolderOnMainIsolate(
Session session,
AccountId accountId,
MailboxId mailboxId,
@@ -158,7 +160,7 @@ class ThreadIsolateWorker {
newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList();
}
log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ${newEmailList.length}');
log('ThreadIsolateWorker::_emptyMailboxFolderOnMainIsolate(): ${newEmailList.length}');
if (newEmailList.isNotEmpty) {
lastEmail = newEmailList.last;
@@ -176,7 +178,7 @@ class ThreadIsolateWorker {
hasEmails = false;
}
}
log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): TOTAL_REMOVE: ${emailListCompleted.length}');
log('ThreadIsolateWorker::_emptyMailboxFolderOnMainIsolate(): TOTAL_REMOVE: ${emailListCompleted.length}');
return emailListCompleted;
}
}
@@ -31,12 +31,10 @@ class FileUploader {
static const String filePathExtraKey = 'path';
final DioClient _dioClient;
final worker.Executor _isolateExecutor;
final FileUtils _fileUtils;
FileUploader(
this._dioClient,
this._isolateExecutor,
this._fileUtils,
);
@@ -49,8 +47,8 @@ class FileUploader {
StreamController<Either<Failure, Success>>? onSendController,
}
) async {
if (PlatformInfo.isWeb) {
return _handleUploadAttachmentActionOnWeb(
if (PlatformInfo.isWeb || Platform.numberOfProcessors == 1) {
return _handleUploadAttachmentActionOnMainIsolate(
uploadId,
fileInfo,
uploadUri,
@@ -63,31 +61,33 @@ class FileUploader {
throw const CanNotGetRootIsolateToken();
}
return await _isolateExecutor.execute(
arg1: UploadFileArguments(
_dioClient,
_fileUtils,
uploadId,
fileInfo,
uploadUri,
rootIsolateToken,
),
fun1: _handleUploadAttachmentAction,
notification: (value) {
if (value is Success) {
log('FileUploader::uploadAttachment(): onUpdateProgress: $value');
onSendController?.add(Right(value));
}
}
final args = UploadFileArguments(
_dioClient,
_fileUtils,
uploadId,
fileInfo,
uploadUri,
rootIsolateToken,
);
return await worker.workerManager.executeWithPort<Attachment, Success>(
_buildUploadClosure(args),
onMessage: (value) {
log('FileUploader::uploadAttachment(): onUpdateProgress: $value');
onSendController?.add(Right(value));
},
)
.then((value) => value)
.catchError((error) => throw error);
}
}
static Future<Attachment> Function(worker.SendPort) _buildUploadClosure(
UploadFileArguments args,
) => (sendPort) => _handleUploadAttachmentAction(args, sendPort);
static Future<Attachment> _handleUploadAttachmentAction(
UploadFileArguments argsUpload,
worker.TypeSendPort sendPort
UploadFileArguments argsUpload,
worker.SendPort sendPort,
) async {
try {
final rootIsolateToken = argsUpload.isolateToken;
@@ -159,7 +159,7 @@ class FileUploader {
}
}
Future<Attachment> _handleUploadAttachmentActionOnWeb(
Future<Attachment> _handleUploadAttachmentActionOnMainIsolate(
UploadTaskId uploadId,
FileInfo fileInfo,
Uri uploadUri,
@@ -188,7 +188,7 @@ class FileUploader {
data: BodyBytesStream.fromBytes(fileInfo.bytes!),
cancelToken: cancelToken,
onSendProgress: (count, total) {
log('FileUploader::_handleUploadAttachmentActionOnWeb():onSendProgress: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
log('FileUploader::_handleUploadAttachmentActionOnMainIsolate():onSendProgress: FILE[${uploadId.id}] : { PROGRESS = $count | TOTAL = $total}');
onSendController?.add(
Right(UploadingAttachmentUploadState(
uploadId,
@@ -198,7 +198,7 @@ class FileUploader {
);
}
);
log('FileUploader::_handleUploadAttachmentActionOnWeb(): RESULT_JSON = $resultJson');
log('FileUploader::_handleUploadAttachmentActionOnMainIsolate(): RESULT_JSON = $resultJson');
if (fileInfo.mimeType == FileUtils.TEXT_PLAIN_MIME_TYPE) {
final fileCharset = await _fileUtils.getCharsetFromBytes(fileInfo.bytes!);
return _parsingResponse(
@@ -40,7 +40,6 @@ import 'package:tmail_ui_user/main/exceptions/thrower/remote_exception_thrower.d
import 'package:tmail_ui_user/main/exceptions/thrower/send_email_exception_thrower.dart';
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
import 'package:uuid/uuid.dart';
import 'package:worker_manager/worker_manager.dart';
class NetworkBindings extends Bindings {
@@ -85,7 +84,6 @@ class NetworkBindings extends Bindings {
Get.find<OIDCHttpClient>(),
Get.find<MailboxCacheManager>(),
));
Get.put(Executor());
}
void _bindingInterceptors() {
@@ -24,7 +24,6 @@ import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart';
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
import 'package:uuid/uuid.dart';
import 'package:worker_manager/worker_manager.dart';
class NetworkIsolateBindings extends Bindings {
@@ -96,16 +95,13 @@ class NetworkIsolateBindings extends Bindings {
Get.put(ThreadIsolateWorker(
Get.find<ThreadAPI>(tag: PlatformInfo.isMobile ? BindingTag.isolateTag : null),
Get.find<EmailAPI>(tag: PlatformInfo.isMobile ? BindingTag.isolateTag : null),
Get.find<Executor>(),
));
Get.put(MailboxIsolateWorker(
Get.find<ThreadAPI>(tag: PlatformInfo.isMobile ? BindingTag.isolateTag : null),
Get.find<EmailAPI>(tag: PlatformInfo.isMobile ? BindingTag.isolateTag : null),
Get.find<Executor>(),
));
Get.put(FileUploader(
Get.find<DioClient>(tag: PlatformInfo.isMobile ? BindingTag.isolateTag : null),
Get.find<Executor>(),
Get.find<FileUtils>(),
));
}
+4 -2
View File
@@ -2,7 +2,6 @@ import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/utils/build_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart';
import 'package:tmail_ui_user/main.dart';
import 'package:tmail_ui_user/main/bindings/main_bindings.dart';
@@ -25,7 +24,10 @@ Future<void> runTmailPreload() async {
if (PlatformInfo.isWeb) AssetPreloader.preloadHtmlEditorAssets(),
], eagerError: false);
await Get.find<Executor>().warmUp(log: BuildUtils.isDebugMode);
if (PlatformInfo.isMobile) {
await workerManager.init(dynamicSpawning: true);
workerManager.log = BuildUtils.isDebugMode;
}
await CozyIntegration.integrateCozy();
await HiveCacheConfig.instance.initializeEncryptionKey();