mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix: limit android background worker duration (#23566)
* fix: limit each android background run to 20 mins # Conflicts: # mobile/lib/domain/services/background_worker.service.dart * review chages --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
+2
-2
@@ -416,12 +416,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?)))
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
+3
-3
@@ -348,7 +348,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 {
|
||||||
@@ -379,10 +379,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
|
||||||
|
|||||||
@@ -105,46 +105,58 @@ 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) {
|
Timer? cancelTimer;
|
||||||
await backupFuture.timeout(Duration(seconds: maxSeconds - 1), onTimeout: () {});
|
if (backupTimeout != null) {
|
||||||
} else {
|
cancelTimer = Timer(backupTimeout, () {
|
||||||
|
if (!_cancellationToken.isCompleted) {
|
||||||
|
_logger.warning("$debugLabel timed out after ${backupTimeout.inMinutes}m, cancelling backup");
|
||||||
|
_cancellationToken.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
try {
|
||||||
await backupFuture;
|
await backupFuture;
|
||||||
|
} finally {
|
||||||
|
cancelTimer?.cancel();
|
||||||
}
|
}
|
||||||
} 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,7 +189,9 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
final nativeSyncApi = _ref?.read(nativeSyncApiProvider);
|
final nativeSyncApi = _ref?.read(nativeSyncApiProvider);
|
||||||
|
|
||||||
_logger.info("Cleaning up background worker");
|
_logger.info("Cleaning up background worker");
|
||||||
_cancellationToken.complete();
|
if (!_cancellationToken.isCompleted) {
|
||||||
|
_cancellationToken.complete();
|
||||||
|
}
|
||||||
final cleanupFutures = [
|
final cleanupFutures = [
|
||||||
nativeSyncApi?.cancelHashing(),
|
nativeSyncApi?.cancelHashing(),
|
||||||
workerManagerPatch.dispose().catchError((_) async {
|
workerManagerPatch.dispose().catchError((_) async {
|
||||||
|
|||||||
+4
-2
@@ -277,7 +277,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();
|
||||||
|
|
||||||
@@ -323,8 +323,10 @@ 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 {
|
||||||
|
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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user