Delay frozen app sockets close until the cellular modem wakes up
Closing TCP sockets sends RST packets. If the cellular modem is idle,
sending RST packets will wake the modem up and consume battery.
This CL adds delay_destroy_frozen_sockets_version flag.
When this flag and destroy_frozen_sockets_version is enabled,
ConnectivityService delays closing socket until the cellular modem wakes up.
Pending frozen sockets are closed also when cellular network becomes no
longer the default network.
This CL also adds flag status and pending uids to the dump.
Bug: 284900338
Test: FrameworksNetTests
Change-Id: I2562568390dda36d02f72afb3a96f824788964c0
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 04d0b93..60523dd 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -934,6 +934,15 @@
private final Map<String, ApplicationSelfCertifiedNetworkCapabilities>
mSelfCertifiedCapabilityCache = new HashMap<>();
+ // Flag to enable the feature of closing frozen app sockets.
+ private final boolean mDestroyFrozenSockets;
+
+ // Flag to optimize closing frozen app sockets by waiting for the cellular modem to wake up.
+ private final boolean mDelayDestroyFrozenSockets;
+
+ // Uids that ConnectivityService is pending to close sockets of.
+ private final Set<Integer> mPendingFrozenUids = new ArraySet<>();
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -1772,8 +1781,11 @@
mCdmps = null;
}
- if (mDeps.isAtLeastU()
- && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION)) {
+ mDestroyFrozenSockets = mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION);
+ mDelayDestroyFrozenSockets = mDeps.isAtLeastU()
+ && mDeps.isFeatureEnabled(context, DELAY_DESTROY_FROZEN_SOCKETS_VERSION);
+ if (mDestroyFrozenSockets) {
final UidFrozenStateChangedCallback frozenStateChangedCallback =
new UidFrozenStateChangedCallback() {
@Override
@@ -2983,26 +2995,109 @@
}
}
+ /**
+ * Check if the cell network is idle.
+ * @return true if the cell network state is idle
+ * false if the cell network state is active or unknown
+ */
+ private boolean isCellNetworkIdle() {
+ final NetworkAgentInfo defaultNai = getDefaultNetwork();
+ if (defaultNai == null
+ || !defaultNai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ // mNetworkActivityTracker only tracks the activity of the default network. So if the
+ // cell network is not the default network, cell network state is unknown.
+ // TODO(b/279380356): Track cell network state when the cell is not the default network
+ return false;
+ }
+
+ return !mNetworkActivityTracker.isDefaultNetworkActive();
+ }
+
private void handleFrozenUids(int[] uids, int[] frozenStates) {
final ArraySet<Integer> ownerUids = new ArraySet<>();
for (int i = 0; i < uids.length; i++) {
if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
ownerUids.add(uids[i]);
+ } else {
+ mPendingFrozenUids.remove(uids[i]);
}
}
- if (!ownerUids.isEmpty()) {
+ if (ownerUids.isEmpty()) {
+ return;
+ }
+
+ if (mDelayDestroyFrozenSockets && isCellNetworkIdle()) {
+ // Delay closing sockets to avoid waking the cell modem up.
+ // Wi-Fi network state is not considered since waking Wi-Fi modem up is much cheaper
+ // than waking cell modem up.
+ mPendingFrozenUids.addAll(ownerUids);
+ } else {
try {
mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
- } catch (Exception e) {
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
loge("Exception in socket destroy: " + e);
}
}
}
+ private void closePendingFrozenSockets() {
+ ensureRunningOnConnectivityServiceThread();
+
+ try {
+ mDeps.destroyLiveTcpSocketsByOwnerUids(mPendingFrozenUids);
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
+ loge("Failed to close pending frozen app sockets: " + e);
+ }
+ mPendingFrozenUids.clear();
+ }
+
+ private void handleReportNetworkActivity(final NetworkActivityParams params) {
+ mNetworkActivityTracker.handleReportNetworkActivity(params);
+
+ if (mDelayDestroyFrozenSockets
+ && params.isActive
+ && params.label == TRANSPORT_CELLULAR
+ && !mPendingFrozenUids.isEmpty()) {
+ closePendingFrozenSockets();
+ }
+ }
+
+ /**
+ * If the cellular network is no longer the default network, close pending frozen sockets.
+ *
+ * @param newNetwork new default network
+ * @param oldNetwork old default network
+ */
+ private void maybeClosePendingFrozenSockets(NetworkAgentInfo newNetwork,
+ NetworkAgentInfo oldNetwork) {
+ final boolean isOldNetworkCellular = oldNetwork != null
+ && oldNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+ final boolean isNewNetworkCellular = newNetwork != null
+ && newNetwork.networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
+
+ if (isOldNetworkCellular
+ && !isNewNetworkCellular
+ && !mPendingFrozenUids.isEmpty()) {
+ closePendingFrozenSockets();
+ }
+ }
+
+ private void dumpCloseFrozenAppSockets(IndentingPrintWriter pw) {
+ pw.println("CloseFrozenAppSockets:");
+ pw.increaseIndent();
+ pw.print("mDestroyFrozenSockets="); pw.println(mDestroyFrozenSockets);
+ pw.print("mDelayDestroyFrozenSockets="); pw.println(mDelayDestroyFrozenSockets);
+ pw.print("mPendingFrozenUids="); pw.println(mPendingFrozenUids);
+ pw.decreaseIndent();
+ }
+
@VisibleForTesting
static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version";
+ @VisibleForTesting
+ static final String DELAY_DESTROY_FROZEN_SOCKETS_VERSION =
+ "delay_destroy_frozen_sockets_version";
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
@@ -3605,6 +3700,9 @@
dumpAvoidBadWifiSettings(pw);
pw.println();
+ dumpCloseFrozenAppSockets(pw);
+
+ pw.println();
if (!CollectionUtils.contains(args, SHORT_ARG)) {
pw.println();
@@ -4671,6 +4769,7 @@
// incorrect) behavior.
mNetworkActivityTracker.updateDataActivityTracking(
null /* newNetwork */, nai);
+ maybeClosePendingFrozenSockets(null /* newNetwork */, nai);
ensureNetworkTransitionWakelock(nai.toShortString());
}
}
@@ -5877,7 +5976,7 @@
}
case EVENT_REPORT_NETWORK_ACTIVITY:
final NetworkActivityParams arg = (NetworkActivityParams) msg.obj;
- mNetworkActivityTracker.handleReportNetworkActivity(arg);
+ handleReportNetworkActivity(arg);
break;
case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
handleMobileDataPreferredUidsChanged();
@@ -9117,6 +9216,7 @@
mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
}
mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
+ maybeClosePendingFrozenSockets(newDefaultNetwork, oldDefaultNetwork);
mProxyTracker.setDefaultProxy(null != newDefaultNetwork
? newDefaultNetwork.linkProperties.getHttpProxy() : null);
resetHttpProxyForNonDefaultNetwork(oldDefaultNetwork);