diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index 18154668c6..d6388edd16 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -203,8 +203,8 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { nativeSyncApi?.cancelHashing(), LogService.I.dispose(), Store.dispose(), - backgroundSyncManager?.cancel(immediate: true), - backgroundSyncManager?.cancelLocal(immediate: true), + backgroundSyncManager?.cancel(), + backgroundSyncManager?.cancelLocal(), ]; if (_isar.isOpen) { diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index 4742aa403a..f71eb611fe 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -48,11 +48,11 @@ class BackgroundSyncManager { this.onCloudIdSyncError, }); - Future cancel({bool immediate = false}) async { - _syncTask!.cancel(immediate: immediate); - _syncWebsocketTask!.cancel(immediate: immediate); - _cloudIdSyncTask!.cancel(immediate: immediate); - _linkedAlbumSyncTask!.cancel(immediate: immediate); + Future cancel() async { + _syncTask!.cancel(); + _syncWebsocketTask!.cancel(); + _cloudIdSyncTask!.cancel(); + _linkedAlbumSyncTask!.cancel(); try { await Future.wait( @@ -73,9 +73,9 @@ class BackgroundSyncManager { _linkedAlbumSyncTask = null; } - Future cancelLocal({bool immediate = false}) async { - _hashTask!.cancel(immediate: immediate); - _deviceAlbumSyncTask!.cancel(immediate: immediate); + Future cancelLocal() async { + _hashTask!.cancel(); + _deviceAlbumSyncTask!.cancel(); try { await Future.wait([_hashTask?.future, _deviceAlbumSyncTask?.future].nonNulls); diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 11037e12cd..edb289e1ac 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -222,8 +222,8 @@ class AppLifeCycleNotifier extends StateNotifier { Future.wait([ _ref.read(backgroundWorkerLockServiceProvider).unlock(), _ref.read(nativeSyncApiProvider).cancelHashing(), - _ref.read(backgroundSyncProvider).cancel(immediate: true), - _ref.read(backgroundSyncProvider).cancelLocal(immediate: true), + _ref.read(backgroundSyncProvider).cancel(), + _ref.read(backgroundSyncProvider).cancelLocal(), ]), ); } diff --git a/mobile/lib/utils/isolate.dart b/mobile/lib/utils/isolate.dart index b5c6bc0fdb..143b9ea55f 100644 --- a/mobile/lib/utils/isolate.dart +++ b/mobile/lib/utils/isolate.dart @@ -19,7 +19,7 @@ import 'package:logging/logging.dart'; class CancellableTask { final Future future; - final void Function({bool immediate}) cancel; + final void Function() cancel; const CancellableTask({required this.future, required this.cancel}); @@ -57,7 +57,7 @@ class _ResultMessage extends _IsolateMessage { class _ErrorMessage extends _IsolateMessage { final Object? error; final StackTrace? stackTrace; - const _ErrorMessage(this.error, this.stackTrace); + const _ErrorMessage(this.error, [this.stackTrace]); } class _DoneMessage extends _IsolateMessage { @@ -115,24 +115,14 @@ class _IsolateTaskRunner { } } - void cancel({bool immediate = false}) { + void cancel() { if (_isCancelled || _isCleanedUp) return; _isCancelled = true; dPrint(() => "[$debugLabel] Cancellation requested"); - if (immediate) { - _isolate?.kill(priority: Isolate.immediate); - if (!_completer.isCompleted) { - _completer.completeError(Exception("Isolate task cancelled immediately")); - } - dPrint(() => "[$debugLabel] Isolate killed immediately"); - _cleanup(); - return; - } - _isolateSendPort?.send(const _CancelMessage()); - _cleanupTimeoutTimer = Timer(const Duration(seconds: 2), () { + _cleanupTimeoutTimer = Timer(const Duration(seconds: 4), () { if (!_isCleanedUp) { dPrint(() => "[$debugLabel] Cleanup timeout - force killing isolate"); _isolate?.kill(priority: Isolate.immediate); @@ -196,17 +186,35 @@ class _IsolateTaskRunner { Future get future => _completer.future; } +Future _cleanupResources(ProviderContainer? ref, Isar isar, Drift drift, DriftLogger logDb) async { + try { + final cleanupFutures = [ + Store.dispose(), + LogService.I.dispose(), + logDb.close(), + drift.close(), + if (isar.isOpen) isar.close().catchError((_) => false), + ]; + + ref?.dispose(); + + await Future.wait(cleanupFutures).timeout( + const Duration(seconds: 2), + onTimeout: () { + dPrint(() => "Cleanup timeout - some resources may not be closed"); + return []; + }, + ); + } catch (error, stack) { + dPrint(() => "Error during isolate cleanup: $error with stack: $stack"); + } +} + Future _isolateEntryPoint(_IsolateTaskConfig config) async { final receivePort = ReceivePort(); config.mainSendPort.send(_InitMessage(receivePort.sendPort)); bool isCancelled = false; - final subscription = receivePort.listen((message) { - if (message is _CancelMessage) { - isCancelled = true; - } - }); - ProviderContainer? ref; final Isar isar; final Drift drift; @@ -226,6 +234,20 @@ Future _isolateEntryPoint(_IsolateTaskConfig config) async { return; } + final subscription = receivePort.listen((message) async { + if (message is _CancelMessage) { + isCancelled = true; + try { + receivePort.close(); + await _cleanupResources(ref, isar, drift, logDb); + } catch (error, stack) { + dPrint(() => "Error during isolate cancellation cleanup: $error with stack: $stack"); + } finally { + config.mainSendPort.send(const _ErrorMessage("Isolate task cancelled")); + } + } + }); + final log = Logger("IsolateWorker[${config.debugLabel}]"); try { await runZonedGuarded( @@ -259,24 +281,8 @@ Future _isolateEntryPoint(_IsolateTaskConfig config) async { } finally { try { receivePort.close(); - final cleanupFutures = [ - Store.dispose(), - LogService.I.dispose(), - logDb.close(), - drift.close(), - subscription.cancel(), - if (isar.isOpen) isar.close().catchError((_) => false), - ]; - - ref?.dispose(); - - await Future.wait(cleanupFutures).timeout( - const Duration(seconds: 2), - onTimeout: () { - dPrint(() => "Cleanup timeout - some resources may not be closed"); - return []; - }, - ); + unawaited(subscription.cancel()); + await _cleanupResources(ref, isar, drift, logDb); } catch (error, stack) { dPrint(() => "Error during isolate cleanup: $error with stack: $stack"); } finally {