fix(session): recognise expiry after device sleep by wall clock
_reconcileElapsedTime() runs off a Dart Stopwatch, which is CLOCK_MONOTONIC and does not advance while the device is suspended - exactly the state an RTC_WAKEUP expiry alarm fires in. The "did this drop happen at the deadline" test therefore came out false after any sleep, and the screen-off expiry (the common one) still showed no reason in the UI. Each successful sync now records an absolute DateTime deadline, and the tunnel-dropped branch compares against that instead. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
dd1082c8de
commit
34722c751b
1 file changed
+16
-2
@@ -42,6 +42,11 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
|
|
||||||
int _lastUsedBytes = 0;
|
int _lastUsedBytes = 0;
|
||||||
Stopwatch? _sinceLastSuccessfulSync;
|
Stopwatch? _sinceLastSuccessfulSync;
|
||||||
|
// Wall-clock deadline, kept alongside the Stopwatch because they answer
|
||||||
|
// different questions. The Stopwatch is CLOCK_MONOTONIC: it does not advance
|
||||||
|
// while the device is suspended, which is exactly when an RTC_WAKEUP expiry
|
||||||
|
// alarm fires, so it cannot be used to recognise an expiry after sleep.
|
||||||
|
DateTime? _deadlineAt;
|
||||||
double _currentSpeedKBps = 0.0;
|
double _currentSpeedKBps = 0.0;
|
||||||
|
|
||||||
SessionTimer({required this.vpnConnection}) {
|
SessionTimer({required this.vpnConnection}) {
|
||||||
@@ -145,8 +150,12 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
// in arbitrary phase, so expiry almost always arrives here rather than
|
// in arbitrary phase, so expiry almost always arrives here rather than
|
||||||
// at the countdown check in _tick - without this, the commonest session
|
// at the countdown check in _tick - without this, the commonest session
|
||||||
// end left the UI with nothing to say.
|
// end left the UI with nothing to say.
|
||||||
_reconcileElapsedTime();
|
final deadline = _deadlineAt;
|
||||||
final expired = _hasSyncedOnce && _remainingSeconds <= 2;
|
final expired = _hasSyncedOnce &&
|
||||||
|
deadline != null &&
|
||||||
|
DateTime.now().isAfter(
|
||||||
|
deadline.subtract(const Duration(seconds: 5)),
|
||||||
|
);
|
||||||
// postAlert is false either way: the native side already posted for its
|
// postAlert is false either way: the native side already posted for its
|
||||||
// own deadline, and the notification's Disconnect action is the user
|
// own deadline, and the notification's Disconnect action is the user
|
||||||
// asking, which must never raise a "not protected" alert.
|
// asking, which must never raise a "not protected" alert.
|
||||||
@@ -170,6 +179,7 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
_lastUsedBytes = 0;
|
_lastUsedBytes = 0;
|
||||||
_sinceLastSuccessfulSync?.stop();
|
_sinceLastSuccessfulSync?.stop();
|
||||||
_sinceLastSuccessfulSync = null;
|
_sinceLastSuccessfulSync = null;
|
||||||
|
_deadlineAt = null;
|
||||||
_currentSpeedKBps = 0.0;
|
_currentSpeedKBps = 0.0;
|
||||||
_tickCount = 0;
|
_tickCount = 0;
|
||||||
_hasSyncedOnce = false;
|
_hasSyncedOnce = false;
|
||||||
@@ -260,6 +270,7 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
debugPrint('[Timer] Disconnecting: $reason');
|
debugPrint('[Timer] Disconnecting: $reason');
|
||||||
|
|
||||||
_lastEndMessage = endedMessage;
|
_lastEndMessage = endedMessage;
|
||||||
|
_deadlineAt = null;
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = null;
|
_timer = null;
|
||||||
_currentSpeedKBps = 0.0;
|
_currentSpeedKBps = 0.0;
|
||||||
@@ -340,6 +351,9 @@ class SessionTimer extends ChangeNotifier with WidgetsBindingObserver {
|
|||||||
final elapsedMs = _sinceLastSuccessfulSync?.elapsedMilliseconds;
|
final elapsedMs = _sinceLastSuccessfulSync?.elapsedMilliseconds;
|
||||||
_remainingAtLastSync = expiresValue.toInt();
|
_remainingAtLastSync = expiresValue.toInt();
|
||||||
_remainingSeconds = _remainingAtLastSync;
|
_remainingSeconds = _remainingAtLastSync;
|
||||||
|
_deadlineAt = DateTime.now().add(
|
||||||
|
Duration(seconds: _remainingAtLastSync),
|
||||||
|
);
|
||||||
_usedBytes = _readNonNegativeInt(data['used_bytes'], _usedBytes);
|
_usedBytes = _readNonNegativeInt(data['used_bytes'], _usedBytes);
|
||||||
|
|
||||||
final deltaBytes = _usedBytes - _lastUsedBytes;
|
final deltaBytes = _usedBytes - _lastUsedBytes;
|
||||||
|
|||||||
Reference in new issue
Block a user