use mutex

This commit is contained in:
Alex 2025-07-05 14:22:59 -05:00
parent 2f5d75ce21
commit f8ab533acb
No known key found for this signature in database
GPG Key ID: 53CD082B3A5E1082
2 changed files with 91 additions and 33 deletions

View File

@ -9,8 +9,57 @@ class BackgroundSyncManager {
Cancelable<void>? _deviceAlbumSyncTask; Cancelable<void>? _deviceAlbumSyncTask;
Cancelable<void>? _hashTask; Cancelable<void>? _hashTask;
Completer<void>? _localSyncMutex;
Completer<void>? _remoteSyncMutex;
Completer<void>? _hashMutex;
BackgroundSyncManager(); BackgroundSyncManager();
Future<T> _withMutex<T>(
Completer<void>? Function() getMutex,
void Function(Completer<void>?) setMutex,
Future<T> Function() operation,
) async {
while (getMutex() != null) {
await getMutex()!.future;
}
final mutex = Completer<void>();
setMutex(mutex);
try {
final result = await operation();
return result;
} finally {
setMutex(null);
mutex.complete();
}
}
Future<T> _withLocalSyncMutex<T>(Future<T> Function() operation) {
return _withMutex(
() => _localSyncMutex,
(mutex) => _localSyncMutex = mutex,
operation,
);
}
Future<T> _withRemoteSyncMutex<T>(Future<T> Function() operation) {
return _withMutex(
() => _remoteSyncMutex,
(mutex) => _remoteSyncMutex = mutex,
operation,
);
}
Future<T> _withHashMutex<T>(Future<T> Function() operation) {
return _withMutex(
() => _hashMutex,
(mutex) => _hashMutex = mutex,
operation,
);
}
Future<void> cancel() { Future<void> cancel() {
final futures = <Future>[]; final futures = <Future>[];
@ -25,51 +74,57 @@ class BackgroundSyncManager {
// No need to cancel the task, as it can also be run when the user logs out // No need to cancel the task, as it can also be run when the user logs out
Future<void> syncLocal({bool full = false}) { Future<void> syncLocal({bool full = false}) {
if (_deviceAlbumSyncTask != null) { return _withLocalSyncMutex(() async {
return _deviceAlbumSyncTask!.future; if (_deviceAlbumSyncTask != null) {
} return _deviceAlbumSyncTask!.future;
}
// We use a ternary operator to avoid [_deviceAlbumSyncTask] from being // We use a ternary operator to avoid [_deviceAlbumSyncTask] from being
// captured by the closure passed to [runInIsolateGentle]. // captured by the closure passed to [runInIsolateGentle].
_deviceAlbumSyncTask = full _deviceAlbumSyncTask = full
? runInIsolateGentle( ? runInIsolateGentle(
computation: (ref) => computation: (ref) =>
ref.read(localSyncServiceProvider).sync(full: true), ref.read(localSyncServiceProvider).sync(full: true),
) )
: runInIsolateGentle( : runInIsolateGentle(
computation: (ref) => computation: (ref) =>
ref.read(localSyncServiceProvider).sync(full: false), ref.read(localSyncServiceProvider).sync(full: false),
); );
return _deviceAlbumSyncTask!.whenComplete(() { return _deviceAlbumSyncTask!.whenComplete(() {
_deviceAlbumSyncTask = null; _deviceAlbumSyncTask = null;
});
}); });
} }
// No need to cancel the task, as it can also be run when the user logs out // No need to cancel the task, as it can also be run when the user logs out
Future<void> hashAssets() { Future<void> hashAssets() {
if (_hashTask != null) { return _withHashMutex(() async {
return _hashTask!.future; if (_hashTask != null) {
} return _hashTask!.future;
}
_hashTask = runInIsolateGentle( _hashTask = runInIsolateGentle(
computation: (ref) => ref.read(hashServiceProvider).hashAssets(), computation: (ref) => ref.read(hashServiceProvider).hashAssets(),
); );
return _hashTask!.whenComplete(() { return _hashTask!.whenComplete(() {
_hashTask = null; _hashTask = null;
});
}); });
} }
Future<void> syncRemote() { Future<void> syncRemote() {
if (_syncTask != null) { return _withRemoteSyncMutex(() async {
return _syncTask!.future; if (_syncTask != null) {
} return _syncTask!.future;
}
_syncTask = runInIsolateGentle( _syncTask = runInIsolateGentle(
computation: (ref) => ref.read(syncStreamServiceProvider).sync(), computation: (ref) => ref.read(syncStreamServiceProvider).sync(),
); );
return _syncTask!.whenComplete(() { return _syncTask!.whenComplete(() {
_syncTask = null; _syncTask = null;
});
}); });
} }
} }

View File

@ -73,7 +73,10 @@ class ImmichSliverAppBar extends ConsumerWidget {
onPressed: () => context.pop(), onPressed: () => context.pop(),
), ),
IconButton( IconButton(
onPressed: () => ref.read(backgroundSyncProvider).syncRemote(), onPressed: () {
ref.read(backgroundSyncProvider).syncLocal(full: true);
ref.read(backgroundSyncProvider).syncRemote();
},
icon: const Icon( icon: const Icon(
Icons.sync, Icons.sync,
), ),