Skip stop if keepalive is already in stopping state
In current design, crash has been generated when stop function
has been re-entered to catch unexpected behavior. However,
it is possible to re-enter stop function if the network
disconnection occurs after stopping.
Thus, skip stop if keepalive is already in stopping state.
Test: atest ConnectivityServiceTest#testNattSocketKeepalives \
--rerun-until-failure 60000
Bug: 167332570
Change-Id: Ic7068ad3dc990e957c37b8d87d48ebb6469b101f
This commit is contained in:
@@ -367,6 +367,13 @@ public class KeepaliveTracker {
|
||||
Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
|
||||
}
|
||||
}
|
||||
// Ignore the case when the network disconnects immediately after stop() has been
|
||||
// called and the keepalive code is waiting for the response from the modem. This
|
||||
// might happen when the caller listens for a lower-layer network disconnect
|
||||
// callback and stop the keepalive at that time. But the stop() races with the
|
||||
// stop() generated in ConnectivityService network disconnection code path.
|
||||
if (mStartedState == STOPPING && reason == ERROR_INVALID_NETWORK) return;
|
||||
|
||||
// Store the reason of stopping, and report it after the keepalive is fully stopped.
|
||||
if (mStopReason != ERROR_STOP_REASON_UNINITIALIZED) {
|
||||
throw new IllegalStateException("Unexpected stop reason: " + mStopReason);
|
||||
@@ -380,9 +387,6 @@ public class KeepaliveTracker {
|
||||
// e.g. invalid parameter.
|
||||
cleanupStoppedKeepalive(mNai, mSlot);
|
||||
break;
|
||||
case STOPPING:
|
||||
// Keepalive is already in stopping state, ignore.
|
||||
return;
|
||||
default:
|
||||
mStartedState = STOPPING;
|
||||
switch (mType) {
|
||||
|
||||
@@ -67,6 +67,9 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
private NetworkAgent mNetworkAgent;
|
||||
private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
|
||||
private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
|
||||
// Controls how test network agent is going to wait before responding to keepalive
|
||||
// start/stop. Useful when simulate KeepaliveTracker is waiting for response from modem.
|
||||
private long mKeepaliveResponseDelay = 0L;
|
||||
private Integer mExpectedKeepaliveSlot = null;
|
||||
|
||||
public NetworkAgentWrapper(int transport, LinkProperties linkProperties, Context context)
|
||||
@@ -134,12 +137,17 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
if (mWrapper.mExpectedKeepaliveSlot != null) {
|
||||
assertEquals((int) mWrapper.mExpectedKeepaliveSlot, slot);
|
||||
}
|
||||
onSocketKeepaliveEvent(slot, mWrapper.mStartKeepaliveError);
|
||||
mWrapper.mHandlerThread.getThreadHandler().postDelayed(
|
||||
() -> onSocketKeepaliveEvent(slot, mWrapper.mStartKeepaliveError),
|
||||
mWrapper.mKeepaliveResponseDelay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSocketKeepalive(Message msg) {
|
||||
onSocketKeepaliveEvent(msg.arg1, mWrapper.mStopKeepaliveError);
|
||||
final int slot = msg.arg1;
|
||||
mWrapper.mHandlerThread.getThreadHandler().postDelayed(
|
||||
() -> onSocketKeepaliveEvent(slot, mWrapper.mStopKeepaliveError),
|
||||
mWrapper.mKeepaliveResponseDelay);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -248,6 +256,10 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork {
|
||||
mStopKeepaliveError = reason;
|
||||
}
|
||||
|
||||
public void setKeepaliveResponseDelay(long delay) {
|
||||
mKeepaliveResponseDelay = delay;
|
||||
}
|
||||
|
||||
public void setExpectedKeepaliveSlot(Integer slot) {
|
||||
mExpectedKeepaliveSlot = slot;
|
||||
}
|
||||
|
||||
@@ -4292,6 +4292,32 @@ public class ConnectivityServiceTest {
|
||||
myNet = connectKeepaliveNetwork(lp);
|
||||
mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
|
||||
|
||||
// Check that a stop followed by network disconnects does not result in crash.
|
||||
try (SocketKeepalive ka = mCm.createSocketKeepalive(
|
||||
myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
|
||||
ka.start(validKaInterval);
|
||||
callback.expectStarted();
|
||||
// Delay the response of keepalive events in networkAgent long enough to make sure
|
||||
// the follow-up network disconnection will be processed first.
|
||||
mWiFiNetworkAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS);
|
||||
ka.stop();
|
||||
|
||||
// Make sure the stop has been processed. Wait for executor idle is needed to prevent
|
||||
// flaky since the actual stop call to the service is delegated to executor thread.
|
||||
waitForIdleSerialExecutor(executor, TIMEOUT_MS);
|
||||
waitForIdle();
|
||||
|
||||
mWiFiNetworkAgent.disconnect();
|
||||
mWiFiNetworkAgent.expectDisconnected();
|
||||
callback.expectStopped();
|
||||
callback.assertNoCallback();
|
||||
}
|
||||
|
||||
// Reconnect.
|
||||
waitForIdle();
|
||||
myNet = connectKeepaliveNetwork(lp);
|
||||
mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
|
||||
|
||||
// Check that keepalive slots start from 1 and increment. The first one gets slot 1.
|
||||
mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
|
||||
int srcPort2 = 0;
|
||||
|
||||
Reference in New Issue
Block a user