diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/ios/Runner/AppDelegate.swift index 2f6c2162d8..886283c9a2 100644 --- a/mobile/ios/Runner/AppDelegate.swift +++ b/mobile/ios/Runner/AppDelegate.swift @@ -22,7 +22,7 @@ import workmanager // Register a periodic task in iOS 13+ WorkmanagerPlugin.registerPeriodicTask( withIdentifier: "io.ente.frame.iOSBackgroundAppRefresh", - frequency: NSNumber(value: 15 * 60)) + frequency: NSNumber(value: 60 * 60)) // Retrieve the link from parameters if let url = AppLinks.shared.getLink(launchOptions: launchOptions) { diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index 97c2265ca0..7d9a25ef8a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import "package:adaptive_theme/adaptive_theme.dart"; +import "package:async/async.dart"; import "package:computer/computer.dart"; import 'package:ente_crypto/ente_crypto.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; @@ -57,7 +58,7 @@ const kLastFGTaskHeartBeatTime = "fg_task_hb_time"; const kHeartBeatFrequency = Duration(seconds: 1); const kFGSyncFrequency = Duration(minutes: 5); const kFGHomeWidgetSyncFrequency = Duration(minutes: 15); -const kBGTaskTimeout = Duration(seconds: 25); +const kBGTaskTimeout = Duration(seconds: 45); const kBGPushTimeout = Duration(seconds: 28); const kFGTaskDeathTimeoutInMicroseconds = 5000000; @@ -120,62 +121,57 @@ Future _homeWidgetSync() async { } Future runBackgroundTask(String taskId, {String mode = 'normal'}) async { - if (Platform.isIOS) { - _scheduleSuicide(kBGTaskTimeout, taskId); // To prevent OS from punishing us - } try { - if (await _isRunningInForeground()) { - _logger - .info("Background task triggered when process was already running"); - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - await Configuration.instance.init(); - await NetworkClient.instance.init(packageInfo); - ServiceLocator.instance.init( - prefs, - NetworkClient.instance.enteDio, - NetworkClient.instance.getDio(), - packageInfo, - ); - await CollectionsService.instance.init(prefs); - await FileUploader.instance.init(prefs, true); - LocalFileUpdateService.instance.init(prefs); - AppLifecycleService.instance.init(prefs); + final cancellableOp = CancelableOperation.fromFuture(_runMinimally(taskId)); - await LocalSyncService.instance.init(prefs); - RemoteSyncService.instance.init(prefs); - await SyncService.instance.init(prefs); - - await _sync('bgTaskActiveProcess'); - } else { - await _runWithLogs( - () async { - _logger.info("Starting background task in $mode mode"); - await _runInBackground(taskId); - }, - prefix: "[bg]", + if (Platform.isIOS) { + _scheduleSuicide( + kBGTaskTimeout, + taskId, + cancellableOp, ); } + await cancellableOp.valueOrCancellation(); } catch (e, s) { _logger.severe("Error in background task", e, s); } } -Future _runInBackground(String taskId) async { - if (await _isRunningInForeground()) { - _logger.info("FG task running, skipping BG taskID: $taskId"); - return; +Future _runMinimally(String taskId) async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + if (AppLifecycleService.instance.isForeground) { + _logger.info("Background task triggered when process was already running"); + } else { + _logger.info("Background task triggered when process was not running"); } - _logger.info("[WorkManager] Event received: $taskId"); - _scheduleBGTaskKill(taskId); + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + await Configuration.instance.init(); + await Computer.shared().turnOn(workersCount: 4); + CryptoUtil.init(); + + await NetworkClient.instance.init(packageInfo); + ServiceLocator.instance.init( + prefs, + NetworkClient.instance.enteDio, + NetworkClient.instance.getDio(), + packageInfo, + ); + await CollectionsService.instance.init(prefs); + await FileUploader.instance.init(prefs, true); + LocalFileUpdateService.instance.init(prefs); + AppLifecycleService.instance.init(prefs); + + await LocalSyncService.instance.init(prefs); + RemoteSyncService.instance.init(prefs); + await SyncService.instance.init(prefs); + NotificationService.instance.init(prefs); - await _init(true, via: 'runViaBackgroundTask'); await Future.wait( [ _homeWidgetSync(), () async { updateService.showUpdateNotification().ignore(); - await _sync('bgSync'); + await _sync('bgTaskActiveProcess'); }(), ], ); @@ -379,17 +375,6 @@ Future _scheduleFGSync(String caller) async { }); } -void _scheduleBGTaskKill(String taskId) async { - if (await _isRunningInForeground()) { - _logger.info("Found app in FG, committing seppuku. $taskId"); - await BgTaskUtils.killBGTask(taskId); - return; - } - Future.delayed(kHeartBeatFrequency, () async { - _scheduleBGTaskKill(taskId); - }); -} - Future _isRunningInForeground() async { final prefs = await SharedPreferences.getInstance(); await prefs.reload(); @@ -436,11 +421,16 @@ Future _logFGHeartBeatInfo(SharedPreferences prefs) async { _logger.info('isAlreadyRunningFG: $isRunningInFG, last Beat: $lastRun'); } -void _scheduleSuicide(Duration duration, [String? taskID]) { - final taskIDVal = taskID ?? 'no taskID'; - _logger.warning("Schedule seppuku taskID: $taskIDVal"); - Future.delayed(duration, () { - _logger.warning("TLE, committing seppuku for taskID: $taskIDVal"); - BgTaskUtils.killBGTask(taskID); +void _scheduleSuicide( + Duration duration, + String taskID, + CancelableOperation cancellableOp, +) async { + _logger.warning("Schedule seppuku taskID: $taskID"); + final prefs = await SharedPreferences.getInstance(); + Future.delayed(duration, () async { + _logger.warning("TLE, committing seppuku for taskID: $taskID"); + await BgTaskUtils.releaseResourcesForKill(taskID, prefs); + await cancellableOp.cancel(); }); } diff --git a/mobile/lib/utils/bg_task_utils.dart b/mobile/lib/utils/bg_task_utils.dart index d7ed62c2b5..75cbe3001e 100644 --- a/mobile/lib/utils/bg_task_utils.dart +++ b/mobile/lib/utils/bg_task_utils.dart @@ -7,7 +7,6 @@ import "package:photos/main.dart"; import "package:photos/utils/file_uploader.dart"; import "package:shared_preferences/shared_preferences.dart"; import "package:workmanager/workmanager.dart" as workmanager; -import "package:workmanager/workmanager.dart"; @pragma('vm:entry-point') void callbackDispatcher() { @@ -17,7 +16,8 @@ void callbackDispatcher() { return true; } catch (e) { BgTaskUtils.$.info('Task error: $e'); - await BgTaskUtils.killBGTask(taskName); + final prefs = await SharedPreferences.getInstance(); + await BgTaskUtils.releaseResourcesForKill(taskName, prefs); return Future.error(e.toString()); } }); @@ -26,16 +26,15 @@ void callbackDispatcher() { class BgTaskUtils { static final $ = Logger("BgTaskUtils"); - static Future killBGTask([String? taskId]) async { + static Future releaseResourcesForKill( + String taskId, + SharedPreferences prefs, + ) async { await UploadLocksDB.instance.releaseLocksAcquiredByOwnerBefore( ProcessType.background.toString(), DateTime.now().microsecondsSinceEpoch, ); - final prefs = await SharedPreferences.getInstance(); await prefs.remove(kLastBGTaskHeartBeatTime); - if (taskId != null) { - await Workmanager().cancelByUniqueName(taskId); - } } static Future configureWorkmanager() async { @@ -51,17 +50,21 @@ class BgTaskUtils { $.warning("Configuring Work Manager for background tasks"); const iOSBackgroundAppRefresh = "io.ente.frame.iOSBackgroundAppRefresh"; const androidPeriodicTask = "io.ente.photos.androidPeriodicTask"; - final backgrounTaskIdentifier = + final backgroundTaskIdentifier = Platform.isIOS ? iOSBackgroundAppRefresh : androidPeriodicTask; try { await workmanager.Workmanager().initialize( callbackDispatcher, - isInDebugMode: true, // TODO: Remove when merged to production + isInDebugMode: + true, // TODO(prateekmedia): Remove when merged to production ); await workmanager.Workmanager().registerPeriodicTask( - backgrounTaskIdentifier, - backgrounTaskIdentifier, - frequency: const Duration(minutes: 15), + backgroundTaskIdentifier, + backgroundTaskIdentifier, + frequency: Platform.isIOS + ? const Duration(minutes: 60) + : const Duration(minutes: 15), + // TODO(prateekmedia): uncomment at last // initialDelay: const Duration(minutes: 10), constraints: workmanager.Constraints( networkType: workmanager.NetworkType.connected,