summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2012-05-23 13:12:42 -0700
committerDianne Hackborn <hackbod@google.com>2012-05-29 13:33:09 -0700
commit6ae8d1821822296df0606c9cd1c46708cc21cb58 (patch)
treeeb4b17b255b1f0e78078923474afcaad67755f12 /services
parent3dac02265e42bf176e26b83da430ce15d6fd06df (diff)
downloadframeworks_base-6ae8d1821822296df0606c9cd1c46708cc21cb58.zip
frameworks_base-6ae8d1821822296df0606c9cd1c46708cc21cb58.tar.gz
frameworks_base-6ae8d1821822296df0606c9cd1c46708cc21cb58.tar.bz2
Fix (mostly) issue #5109947: Race condition between retrieving a...
...content provider and updating its oom adj This introduces the concept of an "unstable" reference on a content provider. When holding such a reference (and no normal stable ref), the content provider dying will not cause the client process to be killed. This is used in ContentResolver.query(), .openAssetFileDescriptor(), and .openTypedAssetFileDescriptor() to first access the provider with an unstable reference, and if at the point of calling into the provider we find it is dead then acquiring a new stable reference and doing the operation again. Thus if the provider process dies at any point until we get the result back, our own process will not be killed and we can safely retry the operation. Arguably there is still the potential for a race -- if somehow the provider is killed way late by the OOM killer after the query or open has returned -- but this should now be *extremely* unlikely. We also continue to have the issue with the other calls, but these are much less critical, and the same model can't be used there (we wouldn't want to execute two insert operations for example). The implementation of this required some significant changes to the underlying plumbing of content providers, now keeping track of the two different reference counts, and managing them appropriately. To facilitate this, the activity manager now has a formal connection object for a client reference on a content provider, which hands to the application when opening the provider. These changes have allowed a lot of the code to be cleaned up and subtle issues closed. For example, when a process is crashing, we now have a much better idea of the state of content provider clients (olding a stable ref, unstable ref, or waiting for it to launch), so that we can correctly handle each of these. The client side code is also a fair amount cleaner, though in the future there is more than should be done. In particular, the two ProviderClientRecord and ProviderRefCount classes should be combined into one, part of which is exposed to the ContentResolver internal API as a reference on a content provider with methods for updating reference counts and such. Some day we'll do that. Change-Id: I87b10d1b67573ab899e09ca428f1b556fd669c8c
Diffstat (limited to 'services')
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java470
-rw-r--r--services/java/com/android/server/am/ContentProviderConnection.java87
-rw-r--r--services/java/com/android/server/am/ContentProviderRecord.java102
-rw-r--r--services/java/com/android/server/am/ProcessRecord.java40
-rw-r--r--services/java/com/android/server/am/ProviderMap.java30
5 files changed, 496 insertions, 233 deletions
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 6464d7f..9d9b5b8 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -62,6 +62,7 @@ import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.IContentProvider;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
@@ -1810,12 +1811,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
- if (app.conProviders.size() > 0) {
- for (ContentProviderRecord cpr : app.conProviders.keySet()) {
- if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) {
- updateLruProcessInternalLocked(cpr.proc, oomAdj,
- updateActivityTime, i+1);
- }
+ for (int j=app.conProviders.size()-1; j>=0; j--) {
+ ContentProviderRecord cpr = app.conProviders.get(j).provider;
+ if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) {
+ updateLruProcessInternalLocked(cpr.proc, oomAdj,
+ updateActivityTime, i+1);
}
}
@@ -3742,7 +3742,7 @@ public final class ActivityManagerService extends ActivityManagerNative
N = providers.size();
for (i=0; i<N; i++) {
- removeDyingProviderLocked(null, providers.get(i));
+ removeDyingProviderLocked(null, providers.get(i), true);
}
if (doit) {
@@ -6058,53 +6058,72 @@ public final class ActivityManagerService extends ActivityManagerNative
return msg;
}
- boolean incProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
- IBinder externalProcessToken) {
+ ContentProviderConnection incProviderCountLocked(ProcessRecord r,
+ final ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
if (r != null) {
- Integer cnt = r.conProviders.get(cpr);
- if (DEBUG_PROVIDER) Slog.v(TAG,
- "Adding provider requested by "
- + r.processName + " from process "
- + cpr.info.processName + ": " + cpr.name.flattenToShortString()
- + " cnt=" + (cnt == null ? 1 : cnt));
- if (cnt == null) {
- cpr.clients.add(r);
- r.conProviders.put(cpr, new Integer(1));
- return true;
+ for (int i=0; i<r.conProviders.size(); i++) {
+ ContentProviderConnection conn = r.conProviders.get(i);
+ if (conn.provider == cpr) {
+ if (DEBUG_PROVIDER) Slog.v(TAG,
+ "Adding provider requested by "
+ + r.processName + " from process "
+ + cpr.info.processName + ": " + cpr.name.flattenToShortString()
+ + " scnt=" + conn.stableCount + " uscnt=" + conn.unstableCount);
+ if (stable) {
+ conn.stableCount++;
+ conn.numStableIncs++;
+ } else {
+ conn.unstableCount++;
+ conn.numUnstableIncs++;
+ }
+ return conn;
+ }
+ }
+ ContentProviderConnection conn = new ContentProviderConnection(cpr, r);
+ if (stable) {
+ conn.stableCount = 1;
+ conn.numStableIncs = 1;
} else {
- r.conProviders.put(cpr, new Integer(cnt.intValue()+1));
+ conn.unstableCount = 1;
+ conn.numUnstableIncs = 1;
}
- } else {
- cpr.addExternalProcessHandleLocked(externalProcessToken);
+ cpr.connections.add(conn);
+ r.conProviders.add(conn);
+ return conn;
}
- return false;
+ cpr.addExternalProcessHandleLocked(externalProcessToken);
+ return null;
}
- boolean decProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
- IBinder externalProcessToken) {
- if (r != null) {
- Integer cnt = r.conProviders.get(cpr);
+ boolean decProviderCountLocked(ContentProviderConnection conn,
+ ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
+ if (conn != null) {
+ cpr = conn.provider;
if (DEBUG_PROVIDER) Slog.v(TAG,
"Removing provider requested by "
- + r.processName + " from process "
+ + conn.client.processName + " from process "
+ cpr.info.processName + ": " + cpr.name.flattenToShortString()
- + " cnt=" + cnt);
- if (cnt == null || cnt.intValue() <= 1) {
- cpr.clients.remove(r);
- r.conProviders.remove(cpr);
- return true;
+ + " scnt=" + conn.stableCount + " uscnt=" + conn.unstableCount);
+ if (stable) {
+ conn.stableCount--;
} else {
- r.conProviders.put(cpr, new Integer(cnt.intValue()-1));
+ conn.unstableCount--;
}
- } else {
- cpr.removeExternalProcessHandleLocked(externalProcessToken);
+ if (conn.stableCount == 0 && conn.unstableCount == 0) {
+ cpr.connections.remove(conn);
+ conn.client.conProviders.remove(conn);
+ return true;
+ }
+ return false;
}
+ cpr.removeExternalProcessHandleLocked(externalProcessToken);
return false;
}
private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
- String name, IBinder token) {
+ String name, IBinder token, boolean stable) {
ContentProviderRecord cpr;
+ ContentProviderConnection conn = null;
ProviderInfo cpi = null;
synchronized(this) {
@@ -6135,20 +6154,19 @@ public final class ActivityManagerService extends ActivityManagerNative
// of being published... but it is also allowed to run
// in the caller's process, so don't make a connection
// and just let the caller instantiate its own instance.
- if (cpr.provider != null) {
- // don't give caller the provider object, it needs
- // to make its own.
- cpr = new ContentProviderRecord(cpr);
- }
- return cpr;
+ ContentProviderHolder holder = cpr.newHolder(null);
+ // don't give caller the provider object, it needs
+ // to make its own.
+ holder.provider = null;
+ return holder;
}
final long origId = Binder.clearCallingIdentity();
// In this case the provider instance already exists, so we can
// return it right away.
- final boolean countChanged = incProviderCount(r, cpr, token);
- if (countChanged) {
+ conn = incProviderCountLocked(r, cpr, token, stable);
+ if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
// If this is a perceptible app accessing the provider,
// make sure to count it as being accessed and thus
@@ -6181,7 +6199,7 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.i(TAG,
"Existing provider " + cpr.name.flattenToShortString()
+ " is crashing; detaching " + r);
- boolean lastRef = decProviderCount(r, cpr, token);
+ boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread);
if (!lastRef) {
// This wasn't the last ref our process had on
@@ -6189,6 +6207,7 @@ public final class ActivityManagerService extends ActivityManagerNative
return null;
}
providerRunning = false;
+ conn = null;
}
}
@@ -6251,7 +6270,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// info and allow the caller to instantiate it. Only do
// this if the provider is the same user as the caller's
// process, or can run as root (so can be in any process).
- return cpr;
+ return cpr.newHolder(null);
}
if (DEBUG_PROVIDER) {
@@ -6312,7 +6331,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mProviderMap.putProviderByName(name, cpr);
- incProviderCount(r, cpr, token);
+ conn = incProviderCountLocked(r, cpr, token, stable);
+ if (conn != null) {
+ conn.waiting = true;
+ }
}
}
@@ -6334,16 +6356,23 @@ public final class ActivityManagerService extends ActivityManagerNative
Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp="
+ cpr.launchingApp);
}
+ if (conn != null) {
+ conn.waiting = true;
+ }
cpr.wait();
} catch (InterruptedException ex) {
+ } finally {
+ if (conn != null) {
+ conn.waiting = false;
+ }
}
}
}
- return cpr;
+ return cpr != null ? cpr.newHolder(conn) : null;
}
public final ContentProviderHolder getContentProvider(
- IApplicationThread caller, String name) {
+ IApplicationThread caller, String name, boolean stable) {
enforceNotIsolatedCaller("getContentProvider");
if (caller == null) {
String msg = "null IApplicationThread when getting content provider "
@@ -6352,7 +6381,7 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
- return getContentProviderImpl(caller, name, null);
+ return getContentProviderImpl(caller, name, null, stable);
}
public ContentProviderHolder getContentProviderExternal(String name, IBinder token) {
@@ -6362,45 +6391,30 @@ public final class ActivityManagerService extends ActivityManagerNative
}
private ContentProviderHolder getContentProviderExternalUnchecked(String name,IBinder token) {
- return getContentProviderImpl(null, name, token);
+ return getContentProviderImpl(null, name, token, true);
}
/**
* Drop a content provider from a ProcessRecord's bookkeeping
* @param cpr
*/
- public void removeContentProvider(IApplicationThread caller, String name) {
+ public void removeContentProvider(IBinder connection, boolean stable) {
enforceNotIsolatedCaller("removeContentProvider");
synchronized (this) {
- int userId = UserId.getUserId(Binder.getCallingUid());
- ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId);
- if(cpr == null) {
- // remove from mProvidersByClass
- if (DEBUG_PROVIDER) Slog.v(TAG, name +
- " provider not found in providers list");
- return;
+ ContentProviderConnection conn;
+ try {
+ conn = (ContentProviderConnection)connection;
+ } catch (ClassCastException e) {
+ String msg ="removeContentProvider: " + connection
+ + " not a ContentProviderConnection";
+ Slog.w(TAG, msg);
+ throw new IllegalArgumentException(msg);
}
- final ProcessRecord r = getRecordForAppLocked(caller);
- if (r == null) {
- throw new SecurityException(
- "Unable to find app for caller " + caller +
- " when removing content provider " + name);
+ if (conn == null) {
+ throw new NullPointerException("connection is null");
}
- //update content provider record entry info
- ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name);
- ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
- if (DEBUG_PROVIDER) Slog.v(TAG, "Removing provider requested by "
- + r.info.processName + " from process "
- + localCpr.appInfo.processName);
- if (localCpr.launchingApp == r) {
- //should not happen. taken care of as a local provider
- Slog.w(TAG, "removeContentProvider called on local provider: "
- + cpr.info.name + " in process " + r.processName);
- return;
- } else {
- if (decProviderCount(r, localCpr, null)) {
- updateOomAdjLocked();
- }
+ if (decProviderCountLocked(conn, null, null, stable)) {
+ updateOomAdjLocked();
}
}
}
@@ -6447,7 +6461,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
enforceNotIsolatedCaller("publishContentProviders");
- synchronized(this) {
+ synchronized (this) {
final ProcessRecord r = getRecordForAppLocked(caller);
if (DEBUG_MU)
Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
@@ -6499,6 +6513,103 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ public boolean refContentProvider(IBinder connection, int stable, int unstable) {
+ ContentProviderConnection conn;
+ try {
+ conn = (ContentProviderConnection)connection;
+ } catch (ClassCastException e) {
+ String msg ="refContentProvider: " + connection
+ + " not a ContentProviderConnection";
+ Slog.w(TAG, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (conn == null) {
+ throw new NullPointerException("connection is null");
+ }
+
+ synchronized (this) {
+ if (stable > 0) {
+ conn.numStableIncs += stable;
+ }
+ stable = conn.stableCount + stable;
+ if (stable < 0) {
+ throw new IllegalStateException("stableCount < 0: " + stable);
+ }
+
+ if (unstable > 0) {
+ conn.numUnstableIncs += unstable;
+ }
+ unstable = conn.unstableCount + unstable;
+ if (unstable < 0) {
+ throw new IllegalStateException("unstableCount < 0: " + unstable);
+ }
+
+ if ((stable+unstable) <= 0) {
+ throw new IllegalStateException("ref counts can't go to zero here: stable="
+ + stable + " unstable=" + unstable);
+ }
+ conn.stableCount = stable;
+ conn.unstableCount = unstable;
+ return !conn.dead;
+ }
+ }
+
+ public void unstableProviderDied(IBinder connection) {
+ ContentProviderConnection conn;
+ try {
+ conn = (ContentProviderConnection)connection;
+ } catch (ClassCastException e) {
+ String msg ="refContentProvider: " + connection
+ + " not a ContentProviderConnection";
+ Slog.w(TAG, msg);
+ throw new IllegalArgumentException(msg);
+ }
+ if (conn == null) {
+ throw new NullPointerException("connection is null");
+ }
+
+ // Safely retrieve the content provider associated with the connection.
+ IContentProvider provider;
+ synchronized (this) {
+ provider = conn.provider.provider;
+ }
+
+ if (provider == null) {
+ // Um, yeah, we're way ahead of you.
+ return;
+ }
+
+ // Make sure the caller is being honest with us.
+ if (provider.asBinder().pingBinder()) {
+ // Er, no, still looks good to us.
+ synchronized (this) {
+ Slog.w(TAG, "unstableProviderDied: caller " + Binder.getCallingUid()
+ + " says " + conn + " died, but we don't agree");
+ return;
+ }
+ }
+
+ // Well look at that! It's dead!
+ synchronized (this) {
+ if (conn.provider.provider != provider) {
+ // But something changed... good enough.
+ return;
+ }
+
+ ProcessRecord proc = conn.provider.proc;
+ if (proc == null || proc.thread == null) {
+ // Seems like the process is already cleaned up.
+ return;
+ }
+
+ // As far as we're concerned, this is just like receiving a
+ // death notification... just a bit prematurely.
+ Slog.i(TAG, "Process " + proc.processName + " (pid " + proc.pid
+ + ") early provider death");
+ appDiedLocked(proc, proc.pid, proc.thread);
+ }
+ }
+
public static final void installSystemProviders() {
List<ProviderInfo> providers;
synchronized (mSelf) {
@@ -10515,36 +10626,62 @@ public final class ActivityManagerService extends ActivityManagerNative
app.executingServices.clear();
}
- private final void removeDyingProviderLocked(ProcessRecord proc,
- ContentProviderRecord cpr) {
- synchronized (cpr) {
- cpr.launchingApp = null;
- cpr.notifyAll();
- }
-
- mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid));
- String names[] = cpr.info.authority.split(";");
- for (int j = 0; j < names.length; j++) {
- mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid));
+ private final boolean removeDyingProviderLocked(ProcessRecord proc,
+ ContentProviderRecord cpr, boolean always) {
+ final boolean inLaunching = mLaunchingProviders.contains(cpr);
+
+ if (!inLaunching || always) {
+ synchronized (cpr) {
+ cpr.launchingApp = null;
+ cpr.notifyAll();
+ }
+ mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid));
+ String names[] = cpr.info.authority.split(";");
+ for (int j = 0; j < names.length; j++) {
+ mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid));
+ }
}
-
- Iterator<ProcessRecord> cit = cpr.clients.iterator();
- while (cit.hasNext()) {
- ProcessRecord capp = cit.next();
- if (!capp.persistent && capp.thread != null
- && capp.pid != 0
- && capp.pid != MY_PID) {
- Slog.i(TAG, "Kill " + capp.processName
- + " (pid " + capp.pid + "): provider " + cpr.info.name
- + " in dying process " + (proc != null ? proc.processName : "??"));
- EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid,
- capp.processName, capp.setAdj, "dying provider "
- + cpr.name.toShortString());
- Process.killProcessQuiet(capp.pid);
+
+ for (int i=0; i<cpr.connections.size(); i++) {
+ ContentProviderConnection conn = cpr.connections.get(i);
+ if (conn.waiting) {
+ // If this connection is waiting for the provider, then we don't
+ // need to mess with its process unless we are always removing
+ // or for some reason the provider is not currently launching.
+ if (inLaunching && !always) {
+ continue;
+ }
+ }
+ ProcessRecord capp = conn.client;
+ conn.dead = true;
+ if (conn.stableCount > 0) {
+ if (!capp.persistent && capp.thread != null
+ && capp.pid != 0
+ && capp.pid != MY_PID) {
+ Slog.i(TAG, "Kill " + capp.processName
+ + " (pid " + capp.pid + "): provider " + cpr.info.name
+ + " in dying process " + (proc != null ? proc.processName : "??"));
+ EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid,
+ capp.processName, capp.setAdj, "dying provider "
+ + cpr.name.toShortString());
+ Process.killProcessQuiet(capp.pid);
+ }
+ } else if (capp.thread != null && conn.provider.provider != null) {
+ try {
+ capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
+ } catch (RemoteException e) {
+ }
+ // In the protocol here, we don't expect the client to correctly
+ // clean up this connection, we'll just remove it.
+ cpr.connections.remove(i);
+ conn.client.conProviders.remove(conn);
}
}
-
- mLaunchingProviders.remove(cpr);
+
+ if (inLaunching && always) {
+ mLaunchingProviders.remove(cpr);
+ }
+ return inLaunching;
}
/**
@@ -10590,34 +10727,21 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean restart = false;
- int NL = mLaunchingProviders.size();
-
// Remove published content providers.
if (!app.pubProviders.isEmpty()) {
Iterator<ContentProviderRecord> it = app.pubProviders.values().iterator();
while (it.hasNext()) {
ContentProviderRecord cpr = it.next();
- cpr.provider = null;
- cpr.proc = null;
- // See if someone is waiting for this provider... in which
- // case we don't remove it, but just let it restart.
- int i = 0;
- if (!app.bad && allowRestart) {
- for (; i<NL; i++) {
- if (mLaunchingProviders.get(i) == cpr) {
- restart = true;
- break;
- }
- }
- } else {
- i = NL;
+ final boolean always = app.bad || !allowRestart;
+ if (removeDyingProviderLocked(app, cpr, always) || always) {
+ // We left the provider in the launching list, need to
+ // restart it.
+ restart = true;
}
- if (i >= NL) {
- removeDyingProviderLocked(app, cpr);
- NL = mLaunchingProviders.size();
- }
+ cpr.provider = null;
+ cpr.proc = null;
}
app.pubProviders.clear();
}
@@ -10629,10 +10753,9 @@ public final class ActivityManagerService extends ActivityManagerNative
// Unregister from connected content providers.
if (!app.conProviders.isEmpty()) {
- Iterator it = app.conProviders.keySet().iterator();
- while (it.hasNext()) {
- ContentProviderRecord cpr = (ContentProviderRecord)it.next();
- cpr.clients.remove(app);
+ for (int i=0; i<app.conProviders.size(); i++) {
+ ContentProviderConnection conn = app.conProviders.get(i);
+ conn.provider.connections.remove(conn);
}
app.conProviders.clear();
}
@@ -10643,10 +10766,10 @@ public final class ActivityManagerService extends ActivityManagerNative
// XXX Commented out for now. Trying to figure out a way to reproduce
// the actual situation to identify what is actually going on.
if (false) {
- for (int i=0; i<NL; i++) {
+ for (int i=0; i<mLaunchingProviders.size(); i++) {
ContentProviderRecord cpr = (ContentProviderRecord)
mLaunchingProviders.get(i);
- if (cpr.clients.size() <= 0 && !cpr.hasExternalProcessHandles()) {
+ if (cpr.connections.size() <= 0 && !cpr.hasExternalProcessHandles()) {
synchronized (cpr) {
cpr.launchingApp = null;
cpr.notifyAll();
@@ -10743,7 +10866,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (!alwaysBad && !app.bad) {
restart = true;
} else {
- removeDyingProviderLocked(app, cpr);
+ removeDyingProviderLocked(app, cpr, true);
NL = mLaunchingProviders.size();
}
}
@@ -13948,48 +14071,49 @@ public final class ActivityManagerService extends ActivityManagerNative
while (jt.hasNext() && (adj > ProcessList.FOREGROUND_APP_ADJ
|| schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
ContentProviderRecord cpr = jt.next();
- if (cpr.clients.size() != 0) {
- Iterator<ProcessRecord> kt = cpr.clients.iterator();
- while (kt.hasNext() && adj > ProcessList.FOREGROUND_APP_ADJ) {
- ProcessRecord client = kt.next();
- if (client == app) {
- // Being our own client is not interesting.
- continue;
+ for (int i = cpr.connections.size()-1;
+ i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+ || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE);
+ i--) {
+ ContentProviderConnection conn = cpr.connections.get(i);
+ ProcessRecord client = conn.client;
+ if (client == app) {
+ // Being our own client is not interesting.
+ continue;
+ }
+ int myHiddenAdj = hiddenAdj;
+ if (myHiddenAdj > client.hiddenAdj) {
+ if (client.hiddenAdj > ProcessList.FOREGROUND_APP_ADJ) {
+ myHiddenAdj = client.hiddenAdj;
+ } else {
+ myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ;
}
- int myHiddenAdj = hiddenAdj;
- if (myHiddenAdj > client.hiddenAdj) {
- if (client.hiddenAdj > ProcessList.FOREGROUND_APP_ADJ) {
- myHiddenAdj = client.hiddenAdj;
- } else {
- myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ;
- }
+ }
+ int clientAdj = computeOomAdjLocked(
+ client, myHiddenAdj, TOP_APP, true, doingAll);
+ if (adj > clientAdj) {
+ if (app.hasShownUi && app != mHomeProcess
+ && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ app.adjType = "bg-ui-provider";
+ } else {
+ adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
+ ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
+ app.adjType = "provider";
}
- int clientAdj = computeOomAdjLocked(
- client, myHiddenAdj, TOP_APP, true, doingAll);
- if (adj > clientAdj) {
- if (app.hasShownUi && app != mHomeProcess
- && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
- app.adjType = "bg-ui-provider";
- } else {
- adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
- ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
- app.adjType = "provider";
- }
- if (!client.hidden) {
- app.hidden = false;
- }
- if (client.keeping) {
- app.keeping = true;
- }
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo
- .REASON_PROVIDER_IN_USE;
- app.adjSource = client;
- app.adjSourceOom = clientAdj;
- app.adjTarget = cpr.name;
+ if (!client.hidden) {
+ app.hidden = false;
}
- if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
- schedGroup = Process.THREAD_GROUP_DEFAULT;
+ if (client.keeping) {
+ app.keeping = true;
}
+ app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+ .REASON_PROVIDER_IN_USE;
+ app.adjSource = client;
+ app.adjSourceOom = clientAdj;
+ app.adjTarget = cpr.name;
+ }
+ if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
}
}
// If the provider has external (non-framework) process
diff --git a/services/java/com/android/server/am/ContentProviderConnection.java b/services/java/com/android/server/am/ContentProviderConnection.java
new file mode 100644
index 0000000..84f8f02
--- /dev/null
+++ b/services/java/com/android/server/am/ContentProviderConnection.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.os.Binder;
+
+/**
+ * Represents a link between a content provider and client.
+ */
+public class ContentProviderConnection extends Binder {
+ public final ContentProviderRecord provider;
+ public final ProcessRecord client;
+ public int stableCount;
+ public int unstableCount;
+ // The client of this connection is currently waiting for the provider to appear.
+ // Protected by the provider lock.
+ public boolean waiting;
+ // The provider of this connection is now dead.
+ public boolean dead;
+
+ // For debugging.
+ public int numStableIncs;
+ public int numUnstableIncs;
+
+ public ContentProviderConnection(ContentProviderRecord _provider, ProcessRecord _client) {
+ provider = _provider;
+ client = _client;
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ContentProviderConnection{");
+ toShortString(sb);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public String toShortString() {
+ StringBuilder sb = new StringBuilder(128);
+ toShortString(sb);
+ return sb.toString();
+ }
+
+ public String toClientString() {
+ StringBuilder sb = new StringBuilder(128);
+ toClientString(sb);
+ return sb.toString();
+ }
+
+ public void toShortString(StringBuilder sb) {
+ sb.append(provider.toShortString());
+ sb.append("->");
+ toClientString(sb);
+ }
+
+ public void toClientString(StringBuilder sb) {
+ sb.append(client.toShortString());
+ sb.append(" s");
+ sb.append(stableCount);
+ sb.append("/");
+ sb.append(numStableIncs);
+ sb.append(" u");
+ sb.append(unstableCount);
+ sb.append("/");
+ sb.append(numUnstableIncs);
+ if (waiting) {
+ sb.append(" WAITING");
+ }
+ if (dead) {
+ sb.append(" DEAD");
+ }
+ }
+}
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index 608b09a..fb21b06 100644
--- a/services/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/java/com/android/server/am/ContentProviderRecord.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import android.app.IActivityManager.ContentProviderHolder;
import android.content.ComponentName;
+import android.content.IContentProvider;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.os.IBinder;
@@ -27,28 +28,35 @@ import android.os.RemoteException;
import android.util.Slog;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-class ContentProviderRecord extends ContentProviderHolder {
+class ContentProviderRecord {
+ final ActivityManagerService service;
+ public final ProviderInfo info;
+ final int uid;
+ final ApplicationInfo appInfo;
+ final ComponentName name;
+ public IContentProvider provider;
+ public boolean noReleaseNeeded;
// All attached clients
- final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
+ final ArrayList<ContentProviderConnection> connections
+ = new ArrayList<ContentProviderConnection>();
+ //final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
// Handles for non-framework processes supported by this provider
HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle;
// Count for external process for which we have no handles.
int externalProcessNoHandleCount;
- final ActivityManagerService service;
- final int uid;
- final ApplicationInfo appInfo;
- final ComponentName name;
ProcessRecord proc; // if non-null, hosting process.
ProcessRecord launchingApp; // if non-null, waiting for this app to be launched.
String stringName;
+ String shortStringName;
public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info,
ApplicationInfo ai, ComponentName _name) {
- super(_info);
service = _service;
+ info = _info;
uid = ai.uid;
appInfo = ai;
name = _name;
@@ -56,12 +64,20 @@ class ContentProviderRecord extends ContentProviderHolder {
}
public ContentProviderRecord(ContentProviderRecord cpr) {
- super(cpr.info);
+ service = cpr.service;
+ info = cpr.info;
uid = cpr.uid;
appInfo = cpr.appInfo;
name = cpr.name;
noReleaseNeeded = cpr.noReleaseNeeded;
- service = cpr.service;
+ }
+
+ public ContentProviderHolder newHolder(ContentProviderConnection conn) {
+ ContentProviderHolder holder = new ContentProviderHolder(info);
+ holder.provider = provider;
+ holder.noReleaseNeeded = noReleaseNeeded;
+ holder.connection = conn;
+ return holder;
}
public boolean canRunHere(ProcessRecord app) {
@@ -120,30 +136,51 @@ class ContentProviderRecord extends ContentProviderHolder {
return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0);
}
- void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("package=");
- pw.print(info.applicationInfo.packageName);
- pw.print(" process="); pw.println(info.processName);
+ void dump(PrintWriter pw, String prefix, boolean full) {
+ if (full) {
+ pw.print(prefix); pw.print("package=");
+ pw.print(info.applicationInfo.packageName);
+ pw.print(" process="); pw.println(info.processName);
+ }
pw.print(prefix); pw.print("proc="); pw.println(proc);
if (launchingApp != null) {
pw.print(prefix); pw.print("launchingApp="); pw.println(launchingApp);
}
- pw.print(prefix); pw.print("uid="); pw.print(uid);
- pw.print(" provider="); pw.println(provider);
- pw.print(prefix); pw.print("name="); pw.println(info.authority);
- if (info.isSyncable || info.multiprocess || info.initOrder != 0) {
- pw.print(prefix); pw.print("isSyncable="); pw.print(info.isSyncable);
- pw.print("multiprocess="); pw.print(info.multiprocess);
- pw.print(" initOrder="); pw.println(info.initOrder);
+ if (full) {
+ pw.print(prefix); pw.print("uid="); pw.print(uid);
+ pw.print(" provider="); pw.println(provider);
}
- if (hasExternalProcessHandles()) {
- pw.print(prefix); pw.print("externals=");
- pw.println(externalProcessTokenToHandle.size());
+ pw.print(prefix); pw.print("authority="); pw.println(info.authority);
+ if (full) {
+ if (info.isSyncable || info.multiprocess || info.initOrder != 0) {
+ pw.print(prefix); pw.print("isSyncable="); pw.print(info.isSyncable);
+ pw.print(" multiprocess="); pw.print(info.multiprocess);
+ pw.print(" initOrder="); pw.println(info.initOrder);
+ }
}
- if (clients.size() > 0) {
- pw.print(prefix); pw.println("Clients:");
- for (ProcessRecord cproc : clients) {
- pw.print(prefix); pw.print(" - "); pw.println(cproc.toShortString());
+ if (full) {
+ if (hasExternalProcessHandles()) {
+ pw.print(prefix); pw.print("externals=");
+ pw.println(externalProcessTokenToHandle.size());
+ }
+ } else {
+ if (connections.size() > 0 || externalProcessNoHandleCount > 0) {
+ pw.print(prefix); pw.print(connections.size());
+ pw.print(" connections, "); pw.print(externalProcessNoHandleCount);
+ pw.println(" external handles");
+ }
+ }
+ if (connections.size() > 0) {
+ if (full) {
+ pw.print(prefix); pw.println("Connections:");
+ }
+ for (int i=0; i<connections.size(); i++) {
+ ContentProviderConnection conn = connections.get(i);
+ pw.print(prefix); pw.print(" -> "); pw.println(conn.toClientString());
+ if (conn.provider != this) {
+ pw.print(prefix); pw.print(" *** WRONG PROVIDER: ");
+ pw.println(conn.provider);
+ }
}
}
}
@@ -162,6 +199,17 @@ class ContentProviderRecord extends ContentProviderHolder {
return stringName = sb.toString();
}
+ public String toShortString() {
+ if (shortStringName != null) {
+ return shortStringName;
+ }
+ StringBuilder sb = new StringBuilder(128);
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append('/');
+ sb.append(name.flattenToShortString());
+ return shortStringName = sb.toString();
+ }
+
// This class represents a handle from an external process to a provider.
private class ExternalProcessHandle implements DeathRecipient {
private static final String LOG_TAG = "ExternalProcessHanldle";
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 4529ecc..8a3ba7f 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -125,8 +125,8 @@ class ProcessRecord {
final HashMap<String, ContentProviderRecord> pubProviders
= new HashMap<String, ContentProviderRecord>();
// All ContentProviderRecord process is using
- final HashMap<ContentProviderRecord, Integer> conProviders
- = new HashMap<ContentProviderRecord, Integer>();
+ final ArrayList<ContentProviderConnection> conProviders
+ = new ArrayList<ContentProviderConnection>();
boolean persistent; // always keep this application running?
boolean crashing; // are we in the process of crashing?
@@ -257,25 +257,47 @@ class ProcessRecord {
pw.println();
}
if (activities.size() > 0) {
- pw.print(prefix); pw.print("activities="); pw.println(activities);
+ pw.print(prefix); pw.println("Activities:");
+ for (int i=0; i<activities.size(); i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(activities.get(i));
+ }
}
if (services.size() > 0) {
- pw.print(prefix); pw.print("services="); pw.println(services);
+ pw.print(prefix); pw.println("Services:");
+ for (ServiceRecord sr : services) {
+ pw.print(prefix); pw.print(" - "); pw.println(sr);
+ }
}
if (executingServices.size() > 0) {
- pw.print(prefix); pw.print("executingServices="); pw.println(executingServices);
+ pw.print(prefix); pw.println("Executing Services:");
+ for (ServiceRecord sr : executingServices) {
+ pw.print(prefix); pw.print(" - "); pw.println(sr);
+ }
}
if (connections.size() > 0) {
- pw.print(prefix); pw.print("connections="); pw.println(connections);
+ pw.print(prefix); pw.println("Connections:");
+ for (ConnectionRecord cr : connections) {
+ pw.print(prefix); pw.print(" - "); pw.println(cr);
+ }
}
if (pubProviders.size() > 0) {
- pw.print(prefix); pw.print("pubProviders="); pw.println(pubProviders);
+ pw.print(prefix); pw.println("Published Providers:");
+ for (HashMap.Entry<String, ContentProviderRecord> ent : pubProviders.entrySet()) {
+ pw.print(prefix); pw.print(" - "); pw.println(ent.getKey());
+ pw.print(prefix); pw.print(" -> "); pw.println(ent.getValue());
+ }
}
if (conProviders.size() > 0) {
- pw.print(prefix); pw.print("conProviders="); pw.println(conProviders);
+ pw.print(prefix); pw.println("Connected Providers:");
+ for (int i=0; i<conProviders.size(); i++) {
+ pw.print(prefix); pw.print(" - "); pw.println(conProviders.get(i).toShortString());
+ }
}
if (receivers.size() > 0) {
- pw.print(prefix); pw.print("receivers="); pw.println(receivers);
+ pw.print(prefix); pw.println("Receivers:");
+ for (ReceiverList rl : receivers) {
+ pw.print(prefix); pw.print(" - "); pw.println(rl);
+ }
}
}
diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java
index ccc928f..d148ec3 100644
--- a/services/java/com/android/server/am/ProviderMap.java
+++ b/services/java/com/android/server/am/ProviderMap.java
@@ -177,27 +177,9 @@ public class ProviderMap {
while (it.hasNext()) {
Map.Entry<ComponentName, ContentProviderRecord> e = it.next();
ContentProviderRecord r = e.getValue();
- if (dumpAll) {
- pw.print(" * ");
- pw.println(r);
- r.dump(pw, " ");
- } else {
- pw.print(" * ");
- pw.println(r);
- if (r.proc != null) {
- pw.print(" proc=");
- pw.println(r.proc);
- }
- if (r.launchingApp != null) {
- pw.print(" launchingApp=");
- pw.println(r.launchingApp);
- }
- if (r.clients.size() > 0 || r.externalProcessNoHandleCount > 0) {
- pw.print(" "); pw.print(r.clients.size());
- pw.print(" clients, "); pw.print(r.externalProcessNoHandleCount);
- pw.println(" external handles");
- }
- }
+ pw.print(" * ");
+ pw.println(r);
+ r.dump(pw, " ", dumpAll);
}
}
@@ -210,7 +192,7 @@ public class ProviderMap {
pw.print(" ");
pw.print(e.getKey());
pw.print(": ");
- pw.println(r);
+ pw.println(r.toShortString());
}
}
@@ -221,10 +203,10 @@ public class ProviderMap {
pw.println(" ");
pw.println(" Published content providers (by class):");
dumpProvidersByClassLocked(pw, dumpAll, mGlobalByClass);
- pw.println("");
}
if (mProvidersByClassPerUser.size() > 1) {
+ pw.println("");
for (int i = 0; i < mProvidersByClassPerUser.size(); i++) {
HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i);
pw.println(" User " + mProvidersByClassPerUser.keyAt(i) + ":");
@@ -321,7 +303,7 @@ public class ProviderMap {
if (r.proc != null) pw.println(r.proc.pid);
else pw.println("(not running)");
if (dumpAll) {
- r.dump(pw, innerPrefix);
+ r.dump(pw, innerPrefix, true);
}
}
if (r.proc != null && r.proc.thread != null) {