fix: do not listen for store updates in isolates (#21947)

* dispose store on isolate cleanup

* do not listen for store updates in isolates

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2025-09-15 01:20:17 +05:30 committed by GitHub
parent 2dcb32f7d0
commit b26b452530
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 29 additions and 15 deletions

View File

@ -7,6 +7,8 @@ import 'package:cancellation_token_http/http.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/network_capability_extensions.dart'; import 'package:immich_mobile/extensions/network_capability_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/generated/intl_keys.g.dart'; import 'package:immich_mobile/generated/intl_keys.g.dart';
@ -27,11 +29,11 @@ import 'package:immich_mobile/services/localization.service.dart';
import 'package:immich_mobile/services/server_info.service.dart'; import 'package:immich_mobile/services/server_info.service.dart';
import 'package:immich_mobile/services/upload.service.dart'; import 'package:immich_mobile/services/upload.service.dart';
import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:worker_manager/worker_manager.dart'; import 'package:worker_manager/worker_manager.dart';
import 'package:immich_mobile/utils/debug_print.dart';
class BackgroundWorkerFgService { class BackgroundWorkerFgService {
final BackgroundWorkerFgHostApi _foregroundHostApi; final BackgroundWorkerFgHostApi _foregroundHostApi;
@ -181,6 +183,8 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
// Discard any errors on the dispose call // Discard any errors on the dispose call
return; return;
}), }),
LogService.I.dispose(),
Store.dispose(),
_drift.close(), _drift.close(),
_driftLogger.close(), _driftLogger.close(),
backgroundSyncManager.cancel(), backgroundSyncManager.cancel(),
@ -269,6 +273,6 @@ Future<void> backgroundSyncNativeEntrypoint() async {
DartPluginRegistrant.ensureInitialized(); DartPluginRegistrant.ensureInitialized();
final (isar, drift, logDB) = await Bootstrap.initDB(); final (isar, drift, logDB) = await Bootstrap.initDB();
await Bootstrap.initDomain(isar, drift, logDB, shouldBufferLogs: false); await Bootstrap.initDomain(isar, drift, logDB, shouldBufferLogs: false, listenStoreUpdates: false);
await BackgroundWorkerBgService(isar: isar, drift: drift, driftLogger: logDB).init(); await BackgroundWorkerBgService(isar: isar, drift: drift, driftLogger: logDB).init();
} }

View File

@ -10,7 +10,7 @@ class StoreService {
/// In-memory cache. Keys are [StoreKey.id] /// In-memory cache. Keys are [StoreKey.id]
final Map<int, Object?> _cache = {}; final Map<int, Object?> _cache = {};
late final StreamSubscription<List<StoreDto>> _storeUpdateSubscription; StreamSubscription<List<StoreDto>>? _storeUpdateSubscription;
StoreService._({required IStoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository; StoreService._({required IStoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository;
@ -24,15 +24,17 @@ class StoreService {
} }
// TODO: Replace the implementation with the one from create after removing the typedef // TODO: Replace the implementation with the one from create after removing the typedef
static Future<StoreService> init({required IStoreRepository storeRepository}) async { static Future<StoreService> init({required IStoreRepository storeRepository, bool listenUpdates = true}) async {
_instance ??= await create(storeRepository: storeRepository); _instance ??= await create(storeRepository: storeRepository, listenUpdates: listenUpdates);
return _instance!; return _instance!;
} }
static Future<StoreService> create({required IStoreRepository storeRepository}) async { static Future<StoreService> create({required IStoreRepository storeRepository, bool listenUpdates = true}) async {
final instance = StoreService._(isarStoreRepository: storeRepository); final instance = StoreService._(isarStoreRepository: storeRepository);
await instance.populateCache(); await instance.populateCache();
instance._storeUpdateSubscription = instance._listenForChange(); if (listenUpdates) {
instance._storeUpdateSubscription = instance._listenForChange();
}
return instance; return instance;
} }
@ -50,8 +52,8 @@ class StoreService {
}); });
/// Disposes the store and cancels the subscription. To reuse the store call init() again /// Disposes the store and cancels the subscription. To reuse the store call init() again
void dispose() async { Future<void> dispose() async {
await _storeUpdateSubscription.cancel(); await _storeUpdateSubscription?.cancel();
_cache.clear(); _cache.clear();
} }

View File

@ -28,11 +28,11 @@ import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/services/localization.service.dart'; import 'package:immich_mobile/services/localization.service.dart';
import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/backup_progress.dart';
import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/diff.dart'; import 'package:immich_mobile/utils/diff.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:path_provider_foundation/path_provider_foundation.dart'; import 'package:path_provider_foundation/path_provider_foundation.dart';
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
import 'package:immich_mobile/utils/debug_print.dart';
final backgroundServiceProvider = Provider((ref) => BackgroundService()); final backgroundServiceProvider = Provider((ref) => BackgroundService());
@ -331,7 +331,7 @@ class BackgroundService {
Future<bool> _onAssetsChanged() async { Future<bool> _onAssetsChanged() async {
final (isar, drift, logDb) = await Bootstrap.initDB(); final (isar, drift, logDb) = await Bootstrap.initDB();
await Bootstrap.initDomain(isar, drift, logDb); await Bootstrap.initDomain(isar, drift, logDb, shouldBufferLogs: false, listenStoreUpdates: false);
final ref = ProviderContainer( final ref = ProviderContainer(
overrides: [ overrides: [

View File

@ -89,11 +89,17 @@ abstract final class Bootstrap {
return (isar, drift, logDb); return (isar, drift, logDb);
} }
static Future<void> initDomain(Isar db, Drift drift, DriftLogger logDb, {bool shouldBufferLogs = true}) async { static Future<void> initDomain(
Isar db,
Drift drift,
DriftLogger logDb, {
bool listenStoreUpdates = true,
bool shouldBufferLogs = true,
}) async {
final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? true; final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? true;
final IStoreRepository storeRepo = isBeta ? DriftStoreRepository(drift) : IsarStoreRepository(db); final IStoreRepository storeRepo = isBeta ? DriftStoreRepository(drift) : IsarStoreRepository(db);
await StoreService.init(storeRepository: storeRepo); await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
await LogService.init( await LogService.init(
logRepository: LogRepository(logDb), logRepository: LogRepository(logDb),

View File

@ -4,14 +4,15 @@ import 'dart:ui';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/bootstrap.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/http_ssl_options.dart'; import 'package:immich_mobile/utils/http_ssl_options.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:worker_manager/worker_manager.dart'; import 'package:worker_manager/worker_manager.dart';
import 'package:immich_mobile/utils/debug_print.dart';
class InvalidIsolateUsageException implements Exception { class InvalidIsolateUsageException implements Exception {
const InvalidIsolateUsageException(); const InvalidIsolateUsageException();
@ -37,7 +38,7 @@ Cancelable<T?> runInIsolateGentle<T>({
DartPluginRegistrant.ensureInitialized(); DartPluginRegistrant.ensureInitialized();
final (isar, drift, logDb) = await Bootstrap.initDB(); final (isar, drift, logDb) = await Bootstrap.initDB();
await Bootstrap.initDomain(isar, drift, logDb, shouldBufferLogs: false); await Bootstrap.initDomain(isar, drift, logDb, shouldBufferLogs: false, listenStoreUpdates: false);
final ref = ProviderContainer( final ref = ProviderContainer(
overrides: [ overrides: [
// TODO: Remove once isar is removed // TODO: Remove once isar is removed
@ -61,6 +62,7 @@ Cancelable<T?> runInIsolateGentle<T>({
try { try {
ref.dispose(); ref.dispose();
await Store.dispose();
await LogService.I.dispose(); await LogService.I.dispose();
await logDb.close(); await logDb.close();
await drift.close(); await drift.close();