fix: limit each android background run to 20 mins

pull/23566/head
shenlong-tanwen 2025-11-03 23:51:21 +05:30
parent 02456a148e
commit 9029ec5bb6
6 changed files with 46 additions and 32 deletions

View File

@ -295,12 +295,12 @@ class BackgroundWorkerFlutterApi(private val binaryMessenger: BinaryMessenger, p
} }
} }
} }
fun onAndroidUpload(callback: (Result<Unit>) -> Unit) fun onAndroidUpload(maxMinutesArg: Long?, callback: (Result<Unit>) -> Unit)
{ {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$separatedMessageChannelSuffix" val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec) val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(null) { channel.send(listOf(maxMinutesArg)) {
if (it is List<*>) { if (it is List<*>) {
if (it.size > 1) { if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))

View File

@ -107,7 +107,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
* This method acts as a bridge between the native Android background task system and Flutter. * This method acts as a bridge between the native Android background task system and Flutter.
*/ */
override fun onInitialized() { override fun onInitialized() {
flutterApi?.onAndroidUpload { handleHostResult(it) } flutterApi?.onAndroidUpload(maxMinutesArg = 20) { handleHostResult(it) }
} }
// TODO: Move this to a separate NotificationManager class // TODO: Move this to a separate NotificationManager class

View File

@ -295,7 +295,7 @@ class BackgroundWorkerBgHostApiSetup {
/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift.
protocol BackgroundWorkerFlutterApiProtocol { protocol BackgroundWorkerFlutterApiProtocol {
func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void) func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void)
func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void) func cancel(completion: @escaping (Result<Void, PigeonError>) -> Void)
} }
class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol { class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
@ -326,10 +326,10 @@ class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol {
} }
} }
} }
func onAndroidUpload(completion: @escaping (Result<Void, PigeonError>) -> Void) { func onAndroidUpload(maxMinutes maxMinutesArg: Int64?, completion: @escaping (Result<Void, PigeonError>) -> Void) {
let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload\(messageChannelSuffix)" let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload\(messageChannelSuffix)"
let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec)
channel.sendMessage(nil) { response in channel.sendMessage([maxMinutesArg] as [Any?]) { response in
guard let listResponse = response as? [Any?] else { guard let listResponse = response as? [Any?] else {
completion(.failure(createConnectionError(withChannelName: channelName))) completion(.failure(createConnectionError(withChannelName: channelName)))
return return

View File

@ -122,46 +122,54 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
} }
@override @override
Future<void> onAndroidUpload() async { Future<void> onAndroidUpload(int? maxMinutes) async {
_logger.info('Android background processing started'); final hashTimeout = Duration(minutes: _isBackupEnabled ? 3 : 6);
final sw = Stopwatch()..start(); final backupTimeout = maxMinutes != null ? Duration(minutes: maxMinutes - 1) : null;
try { return _backgroundLoop(
if (!await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6))) { hashTimeout: hashTimeout,
_logger.warning("Remote sync did not complete successfully, skipping backup"); backupTimeout: backupTimeout,
return; debugLabel: 'Android background upload',
} );
await _handleBackup();
} catch (error, stack) {
_logger.severe("Failed to complete Android background processing", error, stack);
} finally {
sw.stop();
_logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s");
await _cleanup();
}
} }
@override @override
Future<void> onIosUpload(bool isRefresh, int? maxSeconds) async { Future<void> onIosUpload(bool isRefresh, int? maxSeconds) async {
_logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s'); final hashTimeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6);
final backupTimeout = maxSeconds != null ? Duration(seconds: maxSeconds - 1) : null;
return _backgroundLoop(hashTimeout: hashTimeout, backupTimeout: backupTimeout, debugLabel: 'iOS background upload');
}
Future<void> _backgroundLoop({
required Duration hashTimeout,
required Duration? backupTimeout,
required String debugLabel,
}) async {
_logger.info(
'$debugLabel started hashTimeout: ${hashTimeout.inSeconds}s, backupTimeout: ${backupTimeout?.inMinutes ?? '~'}m',
);
final sw = Stopwatch()..start(); final sw = Stopwatch()..start();
try { try {
final timeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6); if (!await _syncAssets(hashTimeout: hashTimeout)) {
if (!await _syncAssets(hashTimeout: timeout)) {
_logger.warning("Remote sync did not complete successfully, skipping backup"); _logger.warning("Remote sync did not complete successfully, skipping backup");
return; return;
} }
final backupFuture = _handleBackup(); final backupFuture = _handleBackup();
if (maxSeconds != null) { if (backupTimeout != null) {
await backupFuture.timeout(Duration(seconds: maxSeconds - 1), onTimeout: () {}); await backupFuture.timeout(
backupTimeout,
onTimeout: () {
_cancellationToken.cancel();
},
);
} else { } else {
await backupFuture; await backupFuture;
} }
} catch (error, stack) { } catch (error, stack) {
_logger.severe("Failed to complete iOS background upload", error, stack); _logger.severe("Failed to complete $debugLabel", error, stack);
} finally { } finally {
sw.stop(); sw.stop();
_logger.info("iOS background upload completed in ${sw.elapsed.inSeconds}s"); _logger.info("$debugLabel completed in ${sw.elapsed.inSeconds}s");
await _cleanup(); await _cleanup();
} }
} }

View File

@ -273,7 +273,7 @@ abstract class BackgroundWorkerFlutterApi {
Future<void> onIosUpload(bool isRefresh, int? maxSeconds); Future<void> onIosUpload(bool isRefresh, int? maxSeconds);
Future<void> onAndroidUpload(); Future<void> onAndroidUpload(int? maxMinutes);
Future<void> cancel(); Future<void> cancel();
@ -327,8 +327,14 @@ abstract class BackgroundWorkerFlutterApi {
pigeonVar_channel.setMessageHandler(null); pigeonVar_channel.setMessageHandler(null);
} else { } else {
pigeonVar_channel.setMessageHandler((Object? message) async { pigeonVar_channel.setMessageHandler((Object? message) async {
assert(
message != null,
'Argument for dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload was null.',
);
final List<Object?> args = (message as List<Object?>?)!;
final int? arg_maxMinutes = (args[0] as int?);
try { try {
await api.onAndroidUpload(); await api.onAndroidUpload(arg_maxMinutes);
return wrapResponse(empty: true); return wrapResponse(empty: true);
} on PlatformException catch (e) { } on PlatformException catch (e) {
return wrapResponse(error: e); return wrapResponse(error: e);

View File

@ -47,7 +47,7 @@ abstract class BackgroundWorkerFlutterApi {
// Android Only: Called when the Android background upload is triggered // Android Only: Called when the Android background upload is triggered
@async @async
void onAndroidUpload(); void onAndroidUpload(int? maxMinutes);
@async @async
void cancel(); void cancel();