fix: add timeout diagnostics
This commit is contained in:
@@ -501,6 +501,7 @@ class _SetupScreenState extends State<SetupScreen> {
|
||||
// Check network connectivity early to fast-path airplane mode.
|
||||
// Timeout guards against connectivity_plus hanging on some Android TV devices after force-close.
|
||||
bool hasNetwork;
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Checking network connectivity', category: 'setup'));
|
||||
try {
|
||||
final connectivityResult = await Connectivity().checkConnectivity().timeout(
|
||||
const Duration(seconds: 3),
|
||||
@@ -512,6 +513,8 @@ class _SetupScreenState extends State<SetupScreen> {
|
||||
hasNetwork = true;
|
||||
}
|
||||
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Network check done: hasNetwork=$hasNetwork', category: 'setup'));
|
||||
|
||||
if (hasNetwork) {
|
||||
_setStatus(t.common.refreshingServers);
|
||||
|
||||
@@ -551,6 +554,7 @@ class _SetupScreenState extends State<SetupScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Connecting to ${servers.length} server(s)', category: 'setup'));
|
||||
_setStatus(t.common.connectingToServers);
|
||||
|
||||
// Populate per-server status for splash display
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
import '../../utils/future_extensions.dart';
|
||||
|
||||
import '../../models/companion_remote/remote_command.dart';
|
||||
import '../../models/companion_remote/remote_session.dart';
|
||||
@@ -506,7 +507,7 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
||||
}
|
||||
|
||||
try {
|
||||
final winner = await completer.future.timeout(const Duration(seconds: 10));
|
||||
final winner = await completer.future.namedTimeout(const Duration(seconds: 10), operation: 'CompanionRemote race connect');
|
||||
cleanup();
|
||||
|
||||
// Now set up the proper managed connection on the winning address
|
||||
|
||||
@@ -20,6 +20,7 @@ import '../utils/app_logger.dart';
|
||||
import '../utils/codec_utils.dart';
|
||||
import '../utils/global_key_utils.dart';
|
||||
import '../utils/plex_cache_parser.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
/// Context for a download that's been enqueued with background_downloader.
|
||||
/// Carries metadata needed between enqueue and completion callback.
|
||||
@@ -158,9 +159,11 @@ class DownloadManagerService {
|
||||
/// then scans drift for orphaned items.
|
||||
Future<void> recoverInterruptedDownloads() async {
|
||||
try {
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Initializing FileDownloader', category: 'downloads'));
|
||||
await _initializeFileDownloader();
|
||||
|
||||
// Let background_downloader re-enqueue tasks killed by the OS
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Rescheduling killed tasks', category: 'downloads'));
|
||||
final (rescheduled, _) = await FileDownloader().rescheduleKilledTasks();
|
||||
if (rescheduled.isNotEmpty) {
|
||||
appLogger.i('Rescheduled ${rescheduled.length} killed download task(s)');
|
||||
@@ -206,6 +209,7 @@ class DownloadManagerService {
|
||||
}
|
||||
|
||||
// Scan drift for orphaned items stuck in 'downloading'
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Scanning for orphaned downloads', category: 'downloads'));
|
||||
final allDownloads = await _database.select(_database.downloadedMedia).get();
|
||||
for (final item in allDownloads) {
|
||||
if (item.status == DownloadStatus.downloading.index) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'plex_client.dart';
|
||||
import '../models/plex_config.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../utils/connection_constants.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'plex_auth_service.dart';
|
||||
import 'storage_service.dart';
|
||||
|
||||
@@ -183,6 +184,7 @@ class MultiServerManager {
|
||||
}
|
||||
|
||||
appLogger.i('Connecting to ${servers.length} servers...');
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Connecting to ${servers.length} server(s)', category: 'servers'));
|
||||
|
||||
// Use provided client ID or generate a unique one for this app instance
|
||||
final effectiveClientId = clientIdentifier ?? DateTime.now().millisecondsSinceEpoch.toString();
|
||||
@@ -486,6 +488,7 @@ class MultiServerManager {
|
||||
if (offline.isEmpty) return;
|
||||
|
||||
appLogger.d('Attempting reconnection for ${offline.length} offline servers');
|
||||
Sentry.addBreadcrumb(Breadcrumb(message: 'Reconnecting ${offline.length} offline server(s)', category: 'servers'));
|
||||
|
||||
final futures = offline.map((serverId) {
|
||||
final server = _servers[serverId];
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
extension NamedTimeoutExtension<T> on Future<T> {
|
||||
/// Like [Future.timeout], but the [TimeoutException] includes [operation]
|
||||
/// so crash reports identify which call timed out.
|
||||
Future<T> namedTimeout(Duration timeLimit, {required String operation}) {
|
||||
return timeout(timeLimit, onTimeout: () {
|
||||
throw TimeoutException('$operation timed out', timeLimit);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../utils/future_extensions.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -91,7 +92,7 @@ class _NotInSessionViewState extends State<_NotInSessionView> {
|
||||
final client = HttpClient();
|
||||
client.connectionTimeout = const Duration(seconds: 5);
|
||||
final request = await client.getUrl(Uri.parse(WatchTogetherPeerService.healthUrl));
|
||||
final response = await request.close().timeout(const Duration(seconds: 5));
|
||||
final response = await request.close().namedTimeout(const Duration(seconds: 5), operation: 'WatchTogether health check');
|
||||
final body = await response.transform(const SystemEncoding().decoder).join();
|
||||
client.close();
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../../utils/future_extensions.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
import '../../services/base_peer_service.dart';
|
||||
@@ -280,7 +281,7 @@ class WatchTogetherPeerService with KeepaliveMixin {
|
||||
_sendRaw({'type': 'join', 'sessionId': _sessionId, 'peerId': _myPeerId});
|
||||
}
|
||||
|
||||
await completer.future.timeout(const Duration(seconds: 10));
|
||||
await completer.future.namedTimeout(const Duration(seconds: 10), operation: 'WatchTogether reconnect');
|
||||
appLogger.d('WatchTogether: Reconnected successfully');
|
||||
} catch (e) {
|
||||
appLogger.e('WatchTogether: Reconnect failed', error: e);
|
||||
|
||||
Reference in New Issue
Block a user