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;
|
private long mRetryIn;
|
||||||
/* true while the countdown is parked because the ISP whitelist blocks the server */
|
/* true while the countdown is parked because the ISP whitelist blocks the server */
|
||||||
private boolean mRetryFrozen;
|
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 */
|
/* timestamp of the last network-change-triggered immediate retry */
|
||||||
private long mLastNetworkRetry;
|
private long mLastNetworkRetry;
|
||||||
/* double-start guard: profile and issue time of the last connect() that
|
/* 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
|
* 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.
|
* 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);
|
mRetryTimeout = mRetryIn = mTimeoutProvider.getTimeout(error);
|
||||||
mRetryFrozen = false;
|
mRetryFrozen = false;
|
||||||
|
mRetryDue = SystemClock.elapsedRealtime() + mRetryTimeout;
|
||||||
DiagLog.i(TAG, "error " + error + ", retry in " + (mRetryTimeout / 1000) + "s");
|
DiagLog.i(TAG, "error " + error + ", retry in " + (mRetryTimeout / 1000) + "s");
|
||||||
/* never run two countdown chains in parallel (double-speed countdown,
|
/* never run two countdown chains in parallel (double-speed countdown,
|
||||||
* double notification updates and two reconnects at zero) */
|
* 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
|
* Whether a check counts towards the steady-state backoff of a live
|
||||||
* process: connected, retrying/connecting, or held back by the ISP
|
* process: connected, connecting, or held back by the ISP whitelist. In
|
||||||
* whitelist. In all of these the in-process machinery owns recovery and
|
* all of these the in-process machinery owns recovery and the alarm can
|
||||||
* the alarm can only guard against the process dying - firing every
|
* only guard against the process dying - firing every minute (and waking
|
||||||
* minute (and waking Doze every 10 minutes) for hours of failed retries
|
* Doze every 10 minutes) for hours of whitelist blocking only burns
|
||||||
* or whitelist blocking only burns battery.
|
* battery.
|
||||||
*/
|
*/
|
||||||
static boolean isAliveSteady(Action action, boolean processAlive, boolean parked)
|
static boolean isAliveSteady(Action action, boolean processAlive, boolean parked, boolean retryPending)
|
||||||
{
|
{ /* a pending retry needs the fast wakeup alarm: its countdown stands
|
||||||
return action == Action.NONE && processAlive && !parked;
|
* still while the device sleeps and only this alarm resumes it */
|
||||||
|
return action == Action.NONE && processAlive && !parked && !retryPending;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -190,7 +191,13 @@ public class WatchdogReceiver extends BroadcastReceiver
|
|||||||
Action action = decideAction(enabled, uuid != null, autoExpected, whitelistBlocks, serviceTracking,
|
Action action = decideAction(enabled, uuid != null, autoExpected, whitelistBlocks, serviceTracking,
|
||||||
processAlive, wifiPresent, autoProfileConfigured);
|
processAlive, wifiPresent, autoProfileConfigured);
|
||||||
boolean parked = uuid == null && autoExpected && processAlive && !serviceTracking && action == Action.NONE;
|
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)
|
if (parked)
|
||||||
{
|
{
|
||||||
sParkedChecks++;
|
sParkedChecks++;
|
||||||
|
|||||||
+7
-5
@@ -132,21 +132,23 @@ class WatchdogReceiverTest
|
|||||||
/* retrying/connecting: the service tracks the connection */
|
/* retrying/connecting: the service tracks the connection */
|
||||||
val retrying = decide(expectedProfileSet = true, serviceTracking = true, processAlive = true)
|
val retrying = decide(expectedProfileSet = true, serviceTracking = true, processAlive = true)
|
||||||
assertThat(retrying).isEqualTo(Action.NONE)
|
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 */
|
/* whitelist blocks the server while the process is alive */
|
||||||
val blocked = decide(expectedProfileSet = true, whitelistBlocks = true, processAlive = true)
|
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
|
@Test
|
||||||
fun deadProcessOrRestartNeverBacksOff()
|
fun deadProcessOrRestartNeverBacksOff()
|
||||||
{
|
{
|
||||||
val restart = decide(expectedProfileSet = true, processAlive = false)
|
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 */
|
/* whitelist blocks, but no live process: keep the fast cadence */
|
||||||
val blocked = decide(expectedProfileSet = true, whitelistBlocks = true, processAlive = false)
|
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 */
|
/* 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