mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
Fix watchdog timeout for downloads
This commit is contained in:
@@ -6,8 +6,10 @@ const staleOperationThreshold = Duration(days: 7);
|
|||||||
/// Delay between queueing operations to prevent UI flood
|
/// Delay between queueing operations to prevent UI flood
|
||||||
const batchQueueDelayMs = 50;
|
const batchQueueDelayMs = 50;
|
||||||
|
|
||||||
/// Dart-side watchdog timeout (fallback if native events stop arriving)
|
/// Dart-side watchdog timeout (fallback if native events stop arriving).
|
||||||
const watchdogTimeout = Duration(minutes: 1);
|
/// For downloads, this is measured from the last progress update (activity-based).
|
||||||
|
/// For other phases, this is measured from when the phase started.
|
||||||
|
const watchdogTimeout = Duration(minutes: 2);
|
||||||
|
|
||||||
/// How often the watchdog timer checks for stale operations
|
/// How often the watchdog timer checks for stale operations
|
||||||
const watchdogCheckInterval = Duration(seconds: 30);
|
const watchdogCheckInterval = Duration(seconds: 30);
|
||||||
@@ -42,12 +44,18 @@ class Downloading extends InstallOperation {
|
|||||||
final String taskId;
|
final String taskId;
|
||||||
final DateTime startedAt;
|
final DateTime startedAt;
|
||||||
|
|
||||||
|
/// Last time progress was received. The watchdog uses this (not [startedAt])
|
||||||
|
/// so that slow-but-active downloads are not killed prematurely.
|
||||||
|
final DateTime lastProgressAt;
|
||||||
|
|
||||||
Downloading({
|
Downloading({
|
||||||
required super.target,
|
required super.target,
|
||||||
required this.progress,
|
required this.progress,
|
||||||
required this.taskId,
|
required this.taskId,
|
||||||
DateTime? startedAt,
|
DateTime? startedAt,
|
||||||
}) : startedAt = startedAt ?? DateTime.now();
|
DateTime? lastProgressAt,
|
||||||
|
}) : startedAt = startedAt ?? DateTime.now(),
|
||||||
|
lastProgressAt = lastProgressAt ?? startedAt ?? DateTime.now();
|
||||||
|
|
||||||
Downloading copyWith({double? progress}) {
|
Downloading copyWith({double? progress}) {
|
||||||
return Downloading(
|
return Downloading(
|
||||||
@@ -55,6 +63,8 @@ class Downloading extends InstallOperation {
|
|||||||
progress: progress ?? this.progress,
|
progress: progress ?? this.progress,
|
||||||
taskId: taskId,
|
taskId: taskId,
|
||||||
startedAt: startedAt,
|
startedAt: startedAt,
|
||||||
|
// Reset activity timestamp when progress changes
|
||||||
|
lastProgressAt: progress != null ? DateTime.now() : lastProgressAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,9 +269,11 @@ extension InstallOperationX on InstallOperation {
|
|||||||
/// Whether this operation is still in progress (not terminal)
|
/// Whether this operation is still in progress (not terminal)
|
||||||
bool get isInProgress => !isTerminal;
|
bool get isInProgress => !isTerminal;
|
||||||
|
|
||||||
/// Get start time for watchdog-monitored operations
|
/// Get the relevant timestamp for watchdog monitoring.
|
||||||
DateTime? get startedAt => switch (this) {
|
/// For downloads: last progress update (activity-based, so slow downloads survive).
|
||||||
Downloading(:final startedAt) => startedAt,
|
/// For other phases: when the phase started.
|
||||||
|
DateTime? get watchdogTimestamp => switch (this) {
|
||||||
|
Downloading(:final lastProgressAt) => lastProgressAt,
|
||||||
Verifying(:final startedAt) => startedAt,
|
Verifying(:final startedAt) => startedAt,
|
||||||
Installing(:final startedAt) => startedAt,
|
Installing(:final startedAt) => startedAt,
|
||||||
SystemProcessing(:final startedAt) => startedAt,
|
SystemProcessing(:final startedAt) => startedAt,
|
||||||
|
|||||||
@@ -217,9 +217,9 @@ abstract class PackageManager extends StateNotifier<PackageManagerState> {
|
|||||||
for (final entry in state.operations.entries) {
|
for (final entry in state.operations.entries) {
|
||||||
final appId = entry.key;
|
final appId = entry.key;
|
||||||
final op = entry.value;
|
final op = entry.value;
|
||||||
final startedAt = op.startedAt;
|
final timestamp = op.watchdogTimestamp;
|
||||||
|
|
||||||
if (startedAt == null || now.difference(startedAt) <= watchdogTimeout) {
|
if (timestamp == null || now.difference(timestamp) <= watchdogTimeout) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,8 +135,10 @@ Code audit found several potential hanging state bugs not covered by existing ti
|
|||||||
|
|
||||||
- [x] 5. Add Dart-side watchdog timer as fallback
|
- [x] 5. Add Dart-side watchdog timer as fallback
|
||||||
- Added `startedAt` field to `Verifying` and `SystemProcessing` states
|
- Added `startedAt` field to `Verifying` and `SystemProcessing` states
|
||||||
- Added `needsWatchdog` and `startedAt` getters to extension for uniform access
|
- Added `needsWatchdog` and `watchdogTimestamp` getters to extension for uniform access
|
||||||
- Single 5-minute timeout for all watchdog-monitored states
|
- Activity-based watchdog for downloads: tracks `lastProgressAt` (reset on each progress update) so slow-but-active downloads are not killed
|
||||||
|
- Phase-start-based watchdog for Verifying/Installing/SystemProcessing (uses `startedAt`)
|
||||||
|
- Single 2-minute timeout for all watchdog-monitored states
|
||||||
- Timer runs every 30s, only when there are operations needing watchdog
|
- Timer runs every 30s, only when there are operations needing watchdog
|
||||||
- Transitions stale operations to `OperationFailed` state
|
- Transitions stale operations to `OperationFailed` state
|
||||||
|
|
||||||
@@ -271,10 +273,16 @@ Every state MUST resolve to a terminal state. No exceptions.
|
|||||||
|
|
||||||
### Dart-side Watchdog (Fallback)
|
### Dart-side Watchdog (Fallback)
|
||||||
|
|
||||||
Single 5-minute timeout for all watchdog-monitored states (Verifying, Installing, SystemProcessing).
|
2-minute timeout for all watchdog-monitored states. Timer runs every 30s, only when monitored operations exist.
|
||||||
Timer runs every 30s, only when monitored operations exist.
|
|
||||||
Defense-in-depth for cases where EventChannel breaks or native crashes before sending events.
|
Defense-in-depth for cases where EventChannel breaks or native crashes before sending events.
|
||||||
|
|
||||||
|
| Phase | Measures from | Rationale |
|
||||||
|
|-------|---------------|-----------|
|
||||||
|
| Downloading | Last progress update (`lastProgressAt`) | Slow-but-active downloads must not be killed |
|
||||||
|
| Verifying | Phase start (`startedAt`) | Short operation, elapsed time is appropriate |
|
||||||
|
| Installing | Phase start (`startedAt`) | Short operation, elapsed time is appropriate |
|
||||||
|
| SystemProcessing | Phase start (`startedAt`) | Committed session, elapsed time is appropriate |
|
||||||
|
|
||||||
### Session Tracking Maps
|
### Session Tracking Maps
|
||||||
|
|
||||||
| Map | Purpose |
|
| Map | Purpose |
|
||||||
|
|||||||
Reference in New Issue
Block a user