fix: handle background isolate shutdown on iOS

This commit is contained in:
edde746
2026-03-21 01:43:34 +01:00
parent e95b768350
commit 7ebbf75cb9
5 changed files with 29 additions and 12 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
import 'dart:isolate';
import '../utils/isolate_helper.dart';
import 'dart:typed_data';
import 'plex_client.dart';
@@ -76,7 +76,7 @@ class BifThumbnailService {
appLogger.w('BIF file too large (${bytes.length} bytes), skipping');
return;
}
_entries = await Isolate.run(() => _parseBifBytes(bytes));
_entries = await tryIsolateRun(() => _parseBifBytes(bytes));
} catch (e) {
appLogger.w('BIF download/parse failed', error: e);
}
@@ -1085,6 +1085,7 @@ class DownloadManagerService {
String? errorMessage,
String? currentFile,
}) {
if (_disposed) return;
_progressController.add(
DownloadProgress(
globalKey: globalKey,
@@ -1118,6 +1119,7 @@ class DownloadManagerService {
/// Emit progress update with artwork paths so DownloadProvider can sync
void _emitProgressWithArtwork(String globalKey, {String? thumbPath}) {
if (_disposed) return;
// Emit a progress update containing artwork path
// The status is preserved as downloading since artwork is just one step
_progressController.add(
@@ -1263,6 +1265,7 @@ class DownloadManagerService {
/// Emit deletion progress update
void _emitDeletionProgress(DeletionProgress progress) {
if (_disposed) return;
_deletionProgressController.add(progress);
}
+4 -4
View File
@@ -1,5 +1,5 @@
import 'dart:convert';
import 'dart:isolate';
import '../utils/isolate_helper.dart';
import 'package:drift/drift.dart';
@@ -42,7 +42,7 @@ class PlexApiCache {
final result = await (_db.select(_db.apiCache)..where((t) => t.cacheKey.equals(key))).getSingleOrNull();
if (result != null) {
return await Isolate.run(() => jsonDecode(result.data) as Map<String, dynamic>);
return await tryIsolateRun(() => jsonDecode(result.data) as Map<String, dynamic>);
}
return null;
}
@@ -50,7 +50,7 @@ class PlexApiCache {
/// Cache a response for an endpoint
Future<void> put(String serverId, String endpoint, Map<String, dynamic> data) async {
final key = _buildKey(serverId, endpoint);
final encoded = await Isolate.run(() => jsonEncode(data));
final encoded = await tryIsolateRun(() => jsonEncode(data));
await _db
.into(_db.apiCache)
.insertOnConflictUpdate(ApiCacheCompanion(cacheKey: Value(key), data: Value(encoded), cachedAt: Value(DateTime.now())));
@@ -145,7 +145,7 @@ class PlexApiCache {
if (entries.isEmpty) return {};
return await Isolate.run(() {
return await tryIsolateRun(() {
final result = <String, PlexMetadata>{};
for (final (serverId, ratingKey, rawData) in entries) {
try {
+6 -6
View File
@@ -1,6 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import '../utils/isolate_helper.dart';
import 'dart:math';
import 'package:flutter/foundation.dart';
@@ -112,7 +112,7 @@ class ConnectionTestResult {
ConnectionTestResult({required this.success, required this.latencyMs, this.error});
}
// Top-level function required by compute()
// Top-level function required by tryIsolateRun()
String _decodeUtf8(List<int> bytes) {
return utf8.decode(bytes, allowMalformed: true);
}
@@ -158,7 +158,7 @@ class PlexClient {
ResponseBody responseBody,
) {
if (responseBytes.length > 50 * 1024) {
return compute(_decodeUtf8, responseBytes);
return tryIsolateRun(() => _decodeUtf8(responseBytes));
}
return utf8.decode(responseBytes, allowMalformed: true);
}
@@ -993,7 +993,7 @@ class PlexClient {
final response = await _dio.get('/library/onDeck');
final sid = serverId;
final sname = serverName;
return await Isolate.run(() => _processOnDeckResponse(response.data as Map<String, dynamic>, sid, sname));
return await tryIsolateRun(() => _processOnDeckResponse(response.data as Map<String, dynamic>, sid, sname));
}
/// Get children of a metadata item (e.g., seasons for a show, episodes for a season)
@@ -1481,7 +1481,7 @@ class PlexClient {
);
final sid = serverId;
final sname = serverName;
return await Isolate.run(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
return await tryIsolateRun(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
} catch (e) {
appLogger.e('Failed to get library hubs: $e');
}
@@ -1496,7 +1496,7 @@ class PlexClient {
final response = await _dio.get('/hubs', queryParameters: {'count': limit, 'includeGuids': 1});
final sid = serverId;
final sname = serverName;
return await Isolate.run(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
return await tryIsolateRun(() => _processHubResponse(response.data as Map<String, dynamic>, sid, sname));
} catch (e) {
appLogger.e('Failed to get global hubs: $e');
}
+14
View File
@@ -0,0 +1,14 @@
import 'dart:isolate';
/// Runs [computation] in a background isolate via [Isolate.run].
///
/// Falls back to synchronous execution when the isolate infrastructure is
/// unavailable (e.g. iOS killed background isolates while the app was
/// suspended).
Future<R> tryIsolateRun<R>(R Function() computation) async {
try {
return await Isolate.run(computation);
} on StateError {
return computation();
}
}