From 9210bc85545f31973c957b5179e6a82d05f473c6 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Thu, 5 Sep 2013 12:31:16 -0700 Subject: [PATCH] Implement #10744011: Serialize running of background services Added some code to the activity manager to keep track of services that are launching and limit the number that can be launched concurrently. This only comes into play under specific circumstances: when the service launch is a background request (so timing is not important) and its process is not already running at a high priority. In this case, we have a list of services that are currently launching and when that gets too big we start delaying the launch of future services until currently launching ones are finished. There are some important tuning parameters for this: how many background services we allow to launch concurrently (currently 1 on low-ram devices, 3 on other devices), and how long we wait for a background service to run before consider it to be a more long-running service and go on to the next pending launch (currently set to 15 seconds). Also while in here, did some cleanup of the service code: - A little refactoring to make per-user data cleaner. - Switch to ArrayMap. Change-Id: I09f372eb5e0f81a8de7c64f8320af41e84b90aa3 --- .../com/android/server/am/ActiveServices.java | 503 ++++++++++++------ .../com/android/server/am/ProcessRecord.java | 28 +- .../com/android/server/am/ServiceRecord.java | 17 +- 3 files changed, 393 insertions(+), 155 deletions(-) diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index b96cf926a8355..4521037e117ef 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -20,12 +20,12 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import android.os.Handler; +import android.util.ArrayMap; import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.TransferPipe; @@ -54,7 +54,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -62,6 +61,8 @@ import android.util.TimeUtils; public final class ActiveServices { static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE; static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING; + static final boolean DEBUG_DELAYED_SERVICE = ActivityManagerService.DEBUG_SERVICE; + static final boolean DEBUG_DELAYED_STATS = DEBUG_DELAYED_SERVICE; static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; static final String TAG = ActivityManagerService.TAG; static final String TAG_MU = ActivityManagerService.TAG_MU; @@ -94,16 +95,24 @@ public final class ActiveServices { // LRU background list. static final int MAX_SERVICE_INACTIVITY = 30*60*1000; + // How long we wait for a background started service to stop itself before + // allowing the next pending start to run. + static final int BG_START_TIMEOUT = 15*1000; + final ActivityManagerService mAm; - final ServiceMap mServiceMap = new ServiceMap(); + // Maximum number of services that we allow to start in the background + // at the same time. + final int mMaxStartingBackground; + + final SparseArray mServiceMap = new SparseArray(); /** * All currently bound service connections. Keys are the IBinder of * the client's IServiceConnection. */ - final HashMap> mServiceConnections - = new HashMap>(); + final ArrayMap> mServiceConnections + = new ArrayMap>(); /** * List of services that we have been asked to start, @@ -126,97 +135,127 @@ public final class ActiveServices { final ArrayList mStoppingServices = new ArrayList(); - static class ServiceMap { + static final class DelayingProcess extends ArrayList { + long timeoout; + } - private final SparseArray> mServicesByNamePerUser - = new SparseArray>(); - private final SparseArray> - mServicesByIntentPerUser = new SparseArray< - HashMap>(); + /** + * Information about services for a single user. + */ + class ServiceMap extends Handler { + final ArrayMap mServicesByName + = new ArrayMap(); + final ArrayMap mServicesByIntent + = new ArrayMap(); - ServiceRecord getServiceByName(ComponentName name, int callingUser) { - // TODO: Deal with global services - if (DEBUG_MU) - Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); - return getServices(callingUser).get(name); - } + final ArrayList mDelayedStartList + = new ArrayList(); + /* XXX eventually I'd like to have this based on processes instead of services. + * That is, if we try to start two services in a row both running in the same + * process, this should be one entry in mStartingBackground for that one process + * that remains until all services in it are done. + final ArrayMap mStartingBackgroundMap + = new ArrayMap(); + final ArrayList mStartingProcessList + = new ArrayList(); + */ - ServiceRecord getServiceByName(ComponentName name) { - return getServiceByName(name, -1); - } + final ArrayList mStartingBackground + = new ArrayList(); - ServiceRecord getServiceByIntent(Intent.FilterComparison filter, int callingUser) { - // TODO: Deal with global services - if (DEBUG_MU) - Slog.v(TAG_MU, "getServiceByIntent(" + filter + "), callingUser = " + callingUser); - return getServicesByIntent(callingUser).get(filter); - } + static final int MSG_BG_START_TIMEOUT = 1; - ServiceRecord getServiceByIntent(Intent.FilterComparison filter) { - return getServiceByIntent(filter, -1); - } - - void putServiceByName(ComponentName name, int callingUser, ServiceRecord value) { - // TODO: Deal with global services - getServices(callingUser).put(name, value); - } - - void putServiceByIntent(Intent.FilterComparison filter, int callingUser, - ServiceRecord value) { - // TODO: Deal with global services - getServicesByIntent(callingUser).put(filter, value); - } - - void removeServiceByName(ComponentName name, int callingUser) { - // TODO: Deal with global services - ServiceRecord removed = getServices(callingUser).remove(name); - if (DEBUG_MU) - Slog.v(TAG, "removeServiceByName user=" + callingUser + " name=" + name - + " removed=" + removed); - } - - void removeServiceByIntent(Intent.FilterComparison filter, int callingUser) { - // TODO: Deal with global services - ServiceRecord removed = getServicesByIntent(callingUser).remove(filter); - if (DEBUG_MU) - Slog.v(TAG_MU, "removeServiceByIntent user=" + callingUser + " intent=" + filter - + " removed=" + removed); - } - - Collection getAllServices(int callingUser) { - // TODO: Deal with global services - return getServices(callingUser).values(); - } - - private HashMap getServices(int callingUser) { - HashMap map = mServicesByNamePerUser.get(callingUser); - if (map == null) { - map = new HashMap(); - mServicesByNamePerUser.put(callingUser, map); + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_BG_START_TIMEOUT: { + synchronized (mAm) { + rescheduleDelayedStarts(); + } + } break; } - return map; } - private HashMap getServicesByIntent( - int callingUser) { - HashMap map - = mServicesByIntentPerUser.get(callingUser); - if (map == null) { - map = new HashMap(); - mServicesByIntentPerUser.put(callingUser, map); + void ensureNotStartingBackground(ServiceRecord r) { + if (mStartingBackground.remove(r)) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer background starting: " + r); + rescheduleDelayedStarts(); + } + if (mPendingServices.remove(r)) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer pending start: " + r); + } + } + + void rescheduleDelayedStarts() { + removeMessages(MSG_BG_START_TIMEOUT); + final long now = SystemClock.uptimeMillis(); + for (int i=0, N=mStartingBackground.size(); i 0 + && mStartingBackground.size() < mMaxStartingBackground) { + ServiceRecord r = mDelayedStartList.remove(0); + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r); + if (r.pendingStarts.size() <= 0) { + Slog.w(TAG, "**** NO PENDING STARTS! " + r + " startReq=" + r.startRequested + + " delayedStop=" + r.delayedStop); + } + if (DEBUG_DELAYED_SERVICE) { + if (mDelayedStartList.size() > 0) { + Slog.v(TAG, "Remaining delayed list:"); + for (int i=0; i 0) { + ServiceRecord next = mStartingBackground.get(0); + long when = next.startingBgTimeout > now ? next.startingBgTimeout : now; + if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Top bg start is " + next + + ", can delay others up to " + when); + Message msg = obtainMessage(MSG_BG_START_TIMEOUT); + sendMessageAtTime(msg, when); } - return map; } } public ActiveServices(ActivityManagerService service) { mAm = service; + mMaxStartingBackground = ActivityManager.isLowRamDeviceStatic() ? 1 : 3; + } + + ServiceRecord getServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); + return getServiceMap(callingUser).mServicesByName.get(name); + } + + private ServiceMap getServiceMap(int callingUser) { + ServiceMap smap = mServiceMap.get(callingUser); + if (smap == null) { + smap = new ServiceMap(); + mServiceMap.put(callingUser, smap); + } + return smap; + } + + ArrayMap getServices(int callingUser) { + return getServiceMap(callingUser).mServicesByName; } ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, int userId) { - if (DEBUG_SERVICE) Slog.v(TAG, "startService: " + service + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "startService: " + service + " type=" + resolvedType + " args=" + service.getExtras()); final boolean callerFg; @@ -252,13 +291,67 @@ public final class ActiveServices { } r.lastActivity = SystemClock.uptimeMillis(); r.startRequested = true; + r.delayedStop = false; + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), + service, neededGrants)); + + final ServiceMap smap = getServiceMap(r.userId); + boolean addToStarting = false; + if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) { + ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid); + if (proc == null || proc.curProcState >= ActivityManager.PROCESS_STATE_RECEIVER) { + // If this is not coming from a foreground caller, then we may want + // to delay the start if there are already other background services + // that are starting. This is to avoid process start spam when lots + // of applications are all handling things like connectivity broadcasts. + if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Potential start delay of " + r + " in " + + proc); + if (r.delayed) { + // This service is already scheduled for a delayed start; just leave + // it still waiting. + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Continuing to delay: " + r); + return r.name; + } + if (smap.mStartingBackground.size() >= mMaxStartingBackground) { + // Something else is starting, delay! + Slog.i(TAG, "Delaying start of: " + r); + smap.mDelayedStartList.add(r); + r.delayed = true; + return r.name; + } + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying: " + r); + addToStarting = true; + } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) { + // We slightly loosen when we will enqueue this new service as a background + // starting service we are waiting for, to also include processes that are + // currently running other services. + addToStarting = true; + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying, but counting as bg: " + r); + } else if (DEBUG_DELAYED_STATS) { + Slog.v(TAG, "Not potential delay (state=" + proc.curProcState + + " " + proc.makeAdjReason() + "): " + r); + } + } else if (DEBUG_DELAYED_STATS) { + if (callerFg) { + Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid=" + + callingUid + " pid=" + callingPid + "): " + r); + } else if (r.app != null) { + Slog.v(TAG, "Not potential delay (cur app=" + r.app + "): " + r); + } else { + Slog.v(TAG, "Not potential delay (user " + r.userId + " not started): " + r); + } + } + + return startServiceInnerLocked(smap, service, r, callerFg, addToStarting); + } + + ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, + ServiceRecord r, boolean callerFg, boolean addToStarting) { ProcessStats.ServiceState stracker = r.getTracker(); if (stracker != null) { stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity); } r.callStart = false; - r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), - service, neededGrants)); synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); } @@ -266,10 +359,37 @@ public final class ActiveServices { if (error != null) { return new ComponentName("!!", error); } + + if (r.startRequested && addToStarting) { + boolean first = smap.mStartingBackground.size() == 0; + smap.mStartingBackground.add(r); + r.startingBgTimeout = SystemClock.uptimeMillis() + BG_START_TIMEOUT; + if (DEBUG_DELAYED_SERVICE) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.v(TAG, "Starting background (first=" + first + "): " + r, here); + } else if (DEBUG_DELAYED_STATS) { + Slog.v(TAG, "Starting background (first=" + first + "): " + r); + } + if (first) { + smap.rescheduleDelayedStarts(); + } + } else if (callerFg) { + smap.ensureNotStartingBackground(r); + } + return r.name; } private void stopServiceLocked(ServiceRecord service) { + if (service.delayed) { + // If service isn't actually running, but is is being held in the + // delayed list, then we need to keep it started but note that it + // should be stopped once no longer delayed. + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Delaying stop of pending: " + service); + service.delayedStop = true; + return; + } synchronized (service.stats.getBatteryStats()) { service.stats.stopRunningLocked(); } @@ -409,6 +529,7 @@ public final class ActiveServices { if (r.app != null) { updateServiceForegroundLocked(r.app, true); } + getServiceMap(r.userId).ensureNotStartingBackground(r); } else { if (r.isForeground) { r.isForeground = false; @@ -591,6 +712,9 @@ public final class ActiveServices { } else if (!b.intent.requested) { requestServiceBindingLocked(s, b.intent, callerFg, false); } + + getServiceMap(s.userId).ensureNotStartingBackground(s); + } finally { Binder.restoreCallingIdentity(origId); } @@ -713,7 +837,7 @@ public final class ActiveServices { private final ServiceRecord findServiceLocked(ComponentName name, IBinder token, int userId) { - ServiceRecord r = mServiceMap.getServiceByName(name, userId); + ServiceRecord r = getServiceByName(name, userId); return r == token ? r : null; } @@ -751,12 +875,14 @@ public final class ActiveServices { userId = mAm.handleIncomingUser(callingPid, callingUid, userId, false, true, "service", null); - if (service.getComponent() != null) { - r = mServiceMap.getServiceByName(service.getComponent(), userId); + ServiceMap smap = getServiceMap(userId); + final ComponentName comp = service.getComponent(); + if (comp != null) { + r = smap.mServicesByName.get(comp); } if (r == null) { Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServiceMap.getServiceByIntent(filter, userId); + r = smap.mServicesByIntent.get(filter); } if (r == null) { try { @@ -777,14 +903,15 @@ public final class ActiveServices { if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo, sInfo.name, sInfo.flags)) { userId = 0; + smap = getServiceMap(0); } sInfo = new ServiceInfo(sInfo); sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId); } - r = mServiceMap.getServiceByName(name, userId); + r = smap.mServicesByName.get(name); if (r == null && createIfNeeded) { - Intent.FilterComparison filter = new Intent.FilterComparison( - service.cloneFilter()); + Intent.FilterComparison filter + = new Intent.FilterComparison(service.cloneFilter()); ServiceRestarter res = new ServiceRestarter(); BatteryStatsImpl.Uid.Pkg.Serv ss = null; BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics(); @@ -795,8 +922,8 @@ public final class ActiveServices { } r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res); res.setService(r); - mServiceMap.putServiceByName(name, UserHandle.getUserId(r.appInfo.uid), r); - mServiceMap.putServiceByIntent(filter, UserHandle.getUserId(r.appInfo.uid), r); + smap.mServicesByName.put(name, r); + smap.mServicesByIntent.put(filter, r); // Make sure this component isn't in the pending list. int N = mPendingServices.size(); @@ -842,9 +969,9 @@ public final class ActiveServices { } private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) { - if (DEBUG_SERVICE) Log.v(TAG, ">>> EXECUTING " + if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING " + why + " of " + r + " in app " + r.app); - else if (DEBUG_SERVICE_EXECUTING) Log.v(TAG, ">>> EXECUTING " + else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, ">>> EXECUTING " + why + " of " + r.shortName); long now = SystemClock.uptimeMillis(); if (r.executeNesting == 0) { @@ -1052,6 +1179,13 @@ public final class ActiveServices { // restarting state. mRestartingServices.remove(r); + // Make sure this service is no longer considered delayed, we are starting it now. + if (r.delayed) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r); + getServiceMap(r.userId).mDelayedStartList.remove(r); + r.delayed = false; + } + // Make sure that the user who owns this service is started. If not, // we don't want to allow it to run. if (mAm.mStartedUsers.get(r.userId) == null) { @@ -1126,6 +1260,15 @@ public final class ActiveServices { mPendingServices.add(r); } + if (r.delayedStop) { + // Oh and hey we've already been asked to stop! + r.delayedStop = false; + if (r.startRequested) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r); + stopServiceLocked(r); + } + } + return null; } @@ -1188,6 +1331,21 @@ public final class ActiveServices { } sendServiceArgsLocked(r, execInFg, true); + + if (r.delayed) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r); + getServiceMap(r.userId).mDelayedStartList.remove(r); + r.delayed = false; + } + + if (r.delayedStop) { + // Oh and hey we've already been asked to stop! + r.delayedStop = false; + if (r.startRequested) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (from start): " + r); + stopServiceLocked(r); + } + } } private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, @@ -1246,11 +1404,12 @@ public final class ActiveServices { //Slog.i(TAG, "Bring down service:"); //r.dump(" "); - // Does it still need to run? + // Are we still explicitly being asked to run? if (r.startRequested) { return; } + // Is someone still bound to us keepign us running? if (!knowConn) { hasConn = r.hasAutoCreateConnections(); } @@ -1258,6 +1417,11 @@ public final class ActiveServices { return; } + // Are we in the process of launching? + if (mPendingServices.contains(r)) { + return; + } + bringDownServiceLocked(r); } @@ -1310,8 +1474,9 @@ public final class ActiveServices { EventLogTags.writeAmDestroyService( r.userId, System.identityHashCode(r), (r.app != null) ? r.app.pid : -1); - mServiceMap.removeServiceByName(r.name, r.userId); - mServiceMap.removeServiceByIntent(r.intent, r.userId); + final ServiceMap smap = getServiceMap(r.userId); + smap.mServicesByName.remove(r.name); + smap.mServicesByIntent.remove(r.intent); r.totalRestartCount = 0; unscheduleServiceRestartLocked(r); @@ -1379,6 +1544,8 @@ public final class ActiveServices { r.tracker = null; } } + + smap.ensureNotStartingBackground(r); } void removeConnectionLocked( @@ -1617,10 +1784,11 @@ public final class ActiveServices { private boolean collectForceStopServicesLocked(String name, int userId, boolean evenPersistent, boolean doit, - HashMap services, + ArrayMap services, ArrayList result) { boolean didSomething = false; - for (ServiceRecord service : services.values()) { + for (int i=0; i services = new ArrayList(); if (userId == UserHandle.USER_ALL) { - for (int i=0; i items - = mServiceMap.mServicesByNamePerUser.get(userId); - if (items != null) { + ServiceMap smap = mServiceMap.valueAt(userId); + if (smap != null) { + ArrayMap items = smap.mServicesByName; didSomething = collectForceStopServicesLocked(name, userId, evenPersistent, doit, items, services); } @@ -1668,7 +1836,9 @@ public final class ActiveServices { void cleanUpRemovedTaskLocked(TaskRecord tr, ComponentName component, Intent baseIntent) { ArrayList services = new ArrayList(); - for (ServiceRecord sr : mServiceMap.getAllServices(tr.userId)) { + ArrayMap alls = getServices(tr.userId); + for (int i=0; i 0) { - Iterator it = mServiceMap.getAllServices( - users[ui]).iterator(); - while (it.hasNext() && res.size() < maxNum) { - res.add(makeRunningServiceInfoLocked(it.next())); - } + ArrayMap alls = getServices(users[ui]); + for (int i=0; i 0) { - Iterator it - = mServiceMap.getAllServices(userId).iterator(); - while (it.hasNext() && res.size() < maxNum) { - res.add(makeRunningServiceInfoLocked(it.next())); - } + ArrayMap alls = getServices(userId); + for (int i=0; i=0; conni--) { ArrayList conn = r.connections.valueAt(conni); @@ -1974,28 +2140,27 @@ public final class ActiveServices { try { int[] users = mAm.getUsersLocked(); for (int user : users) { - if (mServiceMap.getAllServices(user).size() > 0) { - boolean printed = false; + ServiceMap smap = getServiceMap(user); + boolean printed = false; + if (smap.mServicesByName.size() > 0) { long nowReal = SystemClock.elapsedRealtime(); - Iterator it = mServiceMap.getAllServices( - user).iterator(); needSep = false; - while (it.hasNext()) { - ServiceRecord r = it.next(); + for (int si=0; si 0) { @@ -2129,31 +2332,27 @@ public final class ActiveServices { } if (dumpAll) { - if (mServiceConnections.size() > 0) { - boolean printed = false; - Iterator> it - = mServiceConnections.values().iterator(); - while (it.hasNext()) { - ArrayList r = it.next(); - for (int i=0; i r = mServiceConnections.valueAt(ic); + for (int i=0; i alls = getServices(user); + for (int i=0; i alls = getServices(user); + for (int i=0; i