Have paused keepalives keep their hardware slot

When a keepalive is paused, it should sit on its hardware
slot for this network to make sure that it is possible to
resume later, even if it means other keepalives can't be
started at the same time.

Test: update AutomaticOnOffKeepaliveTrackerTest for this
Fixes: 268149573
Fixes: 283886067
Change-Id: Ida325bdea198d751483a83ee5d9ec26e39812137
This commit is contained in:
Chalard Jean
2023-05-23 18:23:54 +09:00
committed by Hansen Kurli
parent 12540e13f0
commit ebb0747af3
2 changed files with 66 additions and 41 deletions

View File

@@ -148,8 +148,9 @@ public class KeepaliveTracker {
public static final int TYPE_TCP = 2; public static final int TYPE_TCP = 2;
// Keepalive slot. A small integer that identifies this keepalive among the ones handled // Keepalive slot. A small integer that identifies this keepalive among the ones handled
// by this network. // by this network. This is initialized to NO_KEEPALIVE for new keepalives, but to the
private int mSlot = NO_KEEPALIVE; // old slot for resumed keepalives.
private int mSlot;
// Packet data. // Packet data.
private final KeepalivePacketData mPacket; private final KeepalivePacketData mPacket;
@@ -169,25 +170,30 @@ public class KeepaliveTracker {
int interval, int interval,
int type, int type,
@Nullable FileDescriptor fd) throws InvalidSocketException { @Nullable FileDescriptor fd) throws InvalidSocketException {
this(callback, nai, packet, interval, type, fd, false /* resumed */); this(callback, nai, packet, Binder.getCallingPid(), Binder.getCallingUid(), interval,
type, fd, NO_KEEPALIVE /* slot */, false /* resumed */);
} }
KeepaliveInfo(@NonNull ISocketKeepaliveCallback callback, KeepaliveInfo(@NonNull ISocketKeepaliveCallback callback,
@NonNull NetworkAgentInfo nai, @NonNull NetworkAgentInfo nai,
@NonNull KeepalivePacketData packet, @NonNull KeepalivePacketData packet,
int pid,
int uid,
int interval, int interval,
int type, int type,
@Nullable FileDescriptor fd, @Nullable FileDescriptor fd,
int slot,
boolean resumed) throws InvalidSocketException { boolean resumed) throws InvalidSocketException {
mCallback = callback; mCallback = callback;
mPid = Binder.getCallingPid(); mPid = pid;
mUid = Binder.getCallingUid(); mUid = uid;
mPrivileged = (PERMISSION_GRANTED == mContext.checkPermission(PERMISSION, mPid, mUid)); mPrivileged = (PERMISSION_GRANTED == mContext.checkPermission(PERMISSION, mPid, mUid));
mNai = nai; mNai = nai;
mPacket = packet; mPacket = packet;
mInterval = interval; mInterval = interval;
mType = type; mType = type;
mSlot = slot;
mResumed = resumed; mResumed = resumed;
// For SocketKeepalive, a dup of fd is kept in mFd so the source port from which the // For SocketKeepalive, a dup of fd is kept in mFd so the source port from which the
@@ -468,8 +474,8 @@ public class KeepaliveTracker {
* Construct a new KeepaliveInfo from existing KeepaliveInfo with a new fd. * Construct a new KeepaliveInfo from existing KeepaliveInfo with a new fd.
*/ */
public KeepaliveInfo withFd(@NonNull FileDescriptor fd) throws InvalidSocketException { public KeepaliveInfo withFd(@NonNull FileDescriptor fd) throws InvalidSocketException {
return new KeepaliveInfo(mCallback, mNai, mPacket, mInterval, mType, fd, return new KeepaliveInfo(mCallback, mNai, mPacket, mPid, mUid, mInterval, mType,
true /* resumed */); fd, mSlot, true /* resumed */);
} }
} }
@@ -508,7 +514,9 @@ public class KeepaliveTracker {
*/ */
public int handleStartKeepalive(KeepaliveInfo ki) { public int handleStartKeepalive(KeepaliveInfo ki) {
NetworkAgentInfo nai = ki.getNai(); NetworkAgentInfo nai = ki.getNai();
int slot = findFirstFreeSlot(nai); // If this was a paused keepalive, then reuse the same slot that was kept for it. Otherwise,
// use the first free slot for this network agent.
final int slot = NO_KEEPALIVE != ki.mSlot ? ki.mSlot : findFirstFreeSlot(nai);
mKeepalives.get(nai).put(slot, ki); mKeepalives.get(nai).put(slot, ki);
return ki.start(slot); return ki.start(slot);
} }
@@ -518,6 +526,8 @@ public class KeepaliveTracker {
if (networkKeepalives != null) { if (networkKeepalives != null) {
final ArrayList<KeepaliveInfo> kalist = new ArrayList(networkKeepalives.values()); final ArrayList<KeepaliveInfo> kalist = new ArrayList(networkKeepalives.values());
for (KeepaliveInfo ki : kalist) { for (KeepaliveInfo ki : kalist) {
// Check if keepalive is already stopped
if (ki.mStopReason == SUCCESS_PAUSED) continue;
ki.stop(reason); ki.stop(reason);
// Clean up keepalives since the network agent is disconnected and unable to pass // Clean up keepalives since the network agent is disconnected and unable to pass
// back asynchronous result of stop(). // back asynchronous result of stop().
@@ -556,17 +566,22 @@ public class KeepaliveTracker {
return; return;
} }
// Remove the keepalive from hash table so the slot can be considered available when reusing // If the keepalive was stopped for good, remove it from the hash table so the slot can
// it. // be considered available when reusing it. If it was only a pause, let it sit in the map
networkKeepalives.remove(slot); // so it sits on the slot.
Log.d(TAG, "Remove keepalive " + slot + " on " + networkName + ", " final int reason = ki.mStopReason;
+ networkKeepalives.size() + " remains."); if (reason != SUCCESS_PAUSED) {
networkKeepalives.remove(slot);
Log.d(TAG, "Remove keepalive " + slot + " on " + networkName + ", "
+ networkKeepalives.size() + " remains.");
} else {
Log.d(TAG, "Pause keepalive " + slot + " on " + networkName + ", keep slot reserved");
}
if (networkKeepalives.isEmpty()) { if (networkKeepalives.isEmpty()) {
mKeepalives.remove(nai); mKeepalives.remove(nai);
} }
// Notify app that the keepalive is stopped. // Notify app that the keepalive is stopped.
final int reason = ki.mStopReason;
if (reason == SUCCESS) { if (reason == SUCCESS) {
try { try {
ki.mCallback.onStopped(); ki.mCallback.onStopped();
@@ -612,7 +627,8 @@ public class KeepaliveTracker {
/** /**
* Finalize a paused keepalive. * Finalize a paused keepalive.
* *
* This will send the appropriate callback after checking that this keepalive is indeed paused. * This will send the appropriate callback after checking that this keepalive is indeed paused,
* and free the slot.
* *
* @param ki the keepalive to finalize * @param ki the keepalive to finalize
* @param reason the reason the keepalive is stopped * @param reason the reason the keepalive is stopped
@@ -630,6 +646,13 @@ public class KeepaliveTracker {
} else { } else {
notifyErrorCallback(ki.mCallback, reason); notifyErrorCallback(ki.mCallback, reason);
} }
final HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(ki.mNai);
if (networkKeepalives == null) {
Log.e(TAG, "Attempt to finalize keepalive on nonexistent network " + ki.mNai);
return;
}
networkKeepalives.remove(ki.mSlot);
} }
/** /**

View File

@@ -692,6 +692,10 @@ public class AutomaticOnOffKeepaliveTrackerTest {
assertNull(getAutoKiForBinder(testInfo.binder)); assertNull(getAutoKiForBinder(testInfo.binder));
verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback)); verifyNoMoreInteractions(ignoreStubs(testInfo.socketKeepaliveCallback));
// Make sure the slot is free
final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
checkAndProcessKeepaliveStart(testInfo2.kpd);
} }
@Test @Test
@@ -820,36 +824,34 @@ public class AutomaticOnOffKeepaliveTrackerTest {
clearInvocations(mNai); clearInvocations(mNai);
// Start the second keepalive while the first is paused. // Start the second keepalive while the first is paused.
// TODO: Uncomment the following test after fixing b/283886067. Currently this attempts to final TestKeepaliveInfo testInfo2 = doStartNattKeepalive();
// start the keepalive on TEST_SLOT and this throws in the handler thread. // The slot used is TEST_SLOT + 1 since TEST_SLOT is being taken by the paused keepalive.
// final TestKeepaliveInfo testInfo2 = doStartNattKeepalive(); checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo2.kpd);
// // The slot used is TEST_SLOT + 1 since TEST_SLOT is being taken by the paused keepalive. verify(testInfo2.socketKeepaliveCallback).onStarted();
// checkAndProcessKeepaliveStart(TEST_SLOT + 1, testInfo2.kpd); assertNotNull(getAutoKiForBinder(testInfo2.binder));
// verify(testInfo2.socketKeepaliveCallback).onStarted();
// assertNotNull(getAutoKiForBinder(testInfo2.binder));
// clearInvocations(mNai); clearInvocations(mNai);
// doResumeKeepalive(autoKi1); doResumeKeepalive(autoKi1);
// // Resume on TEST_SLOT. // Resume on TEST_SLOT.
// checkAndProcessKeepaliveStart(TEST_SLOT, testInfo1.kpd); checkAndProcessKeepaliveStart(TEST_SLOT, testInfo1.kpd);
// verify(testInfo1.socketKeepaliveCallback).onResumed(); verify(testInfo1.socketKeepaliveCallback).onResumed();
// clearInvocations(mNai); clearInvocations(mNai);
// doStopKeepalive(autoKi1); doStopKeepalive(autoKi1);
// checkAndProcessKeepaliveStop(TEST_SLOT); checkAndProcessKeepaliveStop(TEST_SLOT);
// verify(testInfo1.socketKeepaliveCallback).onStopped(); verify(testInfo1.socketKeepaliveCallback).onStopped();
// verify(testInfo2.socketKeepaliveCallback, never()).onStopped(); verify(testInfo2.socketKeepaliveCallback, never()).onStopped();
// assertNull(getAutoKiForBinder(testInfo1.binder)); assertNull(getAutoKiForBinder(testInfo1.binder));
// clearInvocations(mNai); clearInvocations(mNai);
// assertNotNull(getAutoKiForBinder(testInfo2.binder)); assertNotNull(getAutoKiForBinder(testInfo2.binder));
// doStopKeepalive(getAutoKiForBinder(testInfo2.binder)); doStopKeepalive(getAutoKiForBinder(testInfo2.binder));
// checkAndProcessKeepaliveStop(TEST_SLOT + 1); checkAndProcessKeepaliveStop(TEST_SLOT + 1);
// verify(testInfo2.socketKeepaliveCallback).onStopped(); verify(testInfo2.socketKeepaliveCallback).onStopped();
// assertNull(getAutoKiForBinder(testInfo2.binder)); assertNull(getAutoKiForBinder(testInfo2.binder));
// verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback)); verifyNoMoreInteractions(ignoreStubs(testInfo1.socketKeepaliveCallback));
// verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback)); verifyNoMoreInteractions(ignoreStubs(testInfo2.socketKeepaliveCallback));
} }
@Test @Test