android: a retry the device slept through is resumed by the watchdog alarm: the countdown runs in uptime and stood still in deep sleep, so a tunnel that dropped overnight came back only when the phone was picked up; while a retry is pending the wakeup alarm stays at 10 min instead of backing off to 1 h
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
b9ea35832c
commit
f075bf00fe
3 files changed
+58
-14
No files matched your search
+35
@@ -90,6 +90,9 @@ public class VpnStateService extends Service
|
||||
private long mRetryIn;
|
||||
/* true while the countdown is parked because the ISP whitelist blocks the server */
|
||||
private boolean mRetryFrozen;
|
||||
/* when the pending retry is due in elapsedRealtime: the countdown itself
|
||||
* runs in uptime and stands still while the device sleeps */
|
||||
private long mRetryDue;
|
||||
/* timestamp of the last network-change-triggered immediate retry */
|
||||
private long mLastNetworkRetry;
|
||||
/* double-start guard: profile and issue time of the last connect() that
|
||||
@@ -610,6 +613,37 @@ public class VpnStateService extends Service
|
||||
});
|
||||
}
|
||||
|
||||
/** Whether a retry countdown is pending (an error with an automatic retry). */
|
||||
public boolean isRetryPending()
|
||||
{
|
||||
return mRetryTimeout > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the pending retry now if its time has passed on the wall clock.
|
||||
* The countdown ticks in uptime, so it stands still while the device
|
||||
* sleeps; the watchdog's wakeup alarm calls this so a tunnel that dropped
|
||||
* overnight comes back without the user picking up the phone. Never
|
||||
* shortens a backoff. Main thread only.
|
||||
*
|
||||
* @return true if a reconnect was started
|
||||
*/
|
||||
public boolean retryIfOverdue()
|
||||
{
|
||||
if (mRetryTimeout <= 0 || mProfile == null || SystemClock.elapsedRealtime() < mRetryDue ||
|
||||
WhiteListManager.getInstance(this).shouldBlockReconnect())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
DiagLog.i(TAG, "retry overdue (device slept through the countdown), reconnecting");
|
||||
mRetryTimeout = 0;
|
||||
mRetryIn = 0;
|
||||
mRetryFrozen = false;
|
||||
mHandler.removeMessages(RETRY_MSG);
|
||||
connect(null, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state and notify all listeners about the change. By using a Handler
|
||||
* this is done from the main UI thread and not the initial reporter thread.
|
||||
@@ -826,6 +860,7 @@ public class VpnStateService extends Service
|
||||
{
|
||||
mRetryTimeout = mRetryIn = mTimeoutProvider.getTimeout(error);
|
||||
mRetryFrozen = false;
|
||||
mRetryDue = SystemClock.elapsedRealtime() + mRetryTimeout;
|
||||
DiagLog.i(TAG, "error " + error + ", retry in " + (mRetryTimeout / 1000) + "s");
|
||||
/* never run two countdown chains in parallel (double-speed countdown,
|
||||
* double notification updates and two reconnects at zero) */
|
||||
|
||||
+16
-9
@@ -154,15 +154,16 @@ public class WatchdogReceiver extends BroadcastReceiver
|
||||
|
||||
/**
|
||||
* Whether a check counts towards the steady-state backoff of a live
|
||||
* process: connected, retrying/connecting, or held back by the ISP
|
||||
* whitelist. In all of these the in-process machinery owns recovery and
|
||||
* the alarm can only guard against the process dying - firing every
|
||||
* minute (and waking Doze every 10 minutes) for hours of failed retries
|
||||
* or whitelist blocking only burns battery.
|
||||
* process: connected, connecting, or held back by the ISP whitelist. In
|
||||
* all of these the in-process machinery owns recovery and the alarm can
|
||||
* only guard against the process dying - firing every minute (and waking
|
||||
* Doze every 10 minutes) for hours of whitelist blocking only burns
|
||||
* battery.
|
||||
*/
|
||||
static boolean isAliveSteady(Action action, boolean processAlive, boolean parked)
|
||||
{
|
||||
return action == Action.NONE && processAlive && !parked;
|
||||
static boolean isAliveSteady(Action action, boolean processAlive, boolean parked, boolean retryPending)
|
||||
{ /* a pending retry needs the fast wakeup alarm: its countdown stands
|
||||
* still while the device sleeps and only this alarm resumes it */
|
||||
return action == Action.NONE && processAlive && !parked && !retryPending;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -190,7 +191,13 @@ public class WatchdogReceiver extends BroadcastReceiver
|
||||
Action action = decideAction(enabled, uuid != null, autoExpected, whitelistBlocks, serviceTracking,
|
||||
processAlive, wifiPresent, autoProfileConfigured);
|
||||
boolean parked = uuid == null && autoExpected && processAlive && !serviceTracking && action == Action.NONE;
|
||||
boolean alive = isAliveSteady(action, processAlive, parked);
|
||||
/* the whitelist parks retries on purpose, that state may back off */
|
||||
boolean retryPending = serviceTracking && !whitelistBlocks && service.isRetryPending();
|
||||
if (retryPending && service.retryIfOverdue())
|
||||
{
|
||||
DiagLog.i(TAG, "resumed a retry the device slept through");
|
||||
}
|
||||
boolean alive = isAliveSteady(action, processAlive, parked, retryPending);
|
||||
if (parked)
|
||||
{
|
||||
sParkedChecks++;
|
||||
|
||||
+7
-5
@@ -132,21 +132,23 @@ class WatchdogReceiverTest
|
||||
/* retrying/connecting: the service tracks the connection */
|
||||
val retrying = decide(expectedProfileSet = true, serviceTracking = true, processAlive = true)
|
||||
assertThat(retrying).isEqualTo(Action.NONE)
|
||||
assertThat(WatchdogReceiver.isAliveSteady(retrying, true, false)).isTrue()
|
||||
assertThat(WatchdogReceiver.isAliveSteady(retrying, true, false, false)).isTrue()
|
||||
/* a pending retry stands still while the device sleeps: keep the fast wakeup alarm */
|
||||
assertThat(WatchdogReceiver.isAliveSteady(retrying, true, false, true)).isFalse()
|
||||
/* whitelist blocks the server while the process is alive */
|
||||
val blocked = decide(expectedProfileSet = true, whitelistBlocks = true, processAlive = true)
|
||||
assertThat(WatchdogReceiver.isAliveSteady(blocked, true, false)).isTrue()
|
||||
assertThat(WatchdogReceiver.isAliveSteady(blocked, true, false, false)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deadProcessOrRestartNeverBacksOff()
|
||||
{
|
||||
val restart = decide(expectedProfileSet = true, processAlive = false)
|
||||
assertThat(WatchdogReceiver.isAliveSteady(restart, false, false)).isFalse()
|
||||
assertThat(WatchdogReceiver.isAliveSteady(restart, false, false, false)).isFalse()
|
||||
/* whitelist blocks, but no live process: keep the fast cadence */
|
||||
val blocked = decide(expectedProfileSet = true, whitelistBlocks = true, processAlive = false)
|
||||
assertThat(WatchdogReceiver.isAliveSteady(blocked, false, false)).isFalse()
|
||||
assertThat(WatchdogReceiver.isAliveSteady(blocked, false, false, false)).isFalse()
|
||||
/* parked sessions use their own backoff */
|
||||
assertThat(WatchdogReceiver.isAliveSteady(Action.NONE, true, true)).isFalse()
|
||||
assertThat(WatchdogReceiver.isAliveSteady(Action.NONE, true, true, false)).isFalse()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user