Merge "Fix races when content providers are acquired and released." into ics-mr1

This commit is contained in:
Jeff Brown
2011-11-14 18:35:57 -08:00
committed by Android (Google) Code Review

View File

@@ -2734,8 +2734,9 @@ public final class ActivityThread {
CharSequence description;
}
private class ProviderRefCount {
private static final class ProviderRefCount {
public int count;
ProviderRefCount(int pCount) {
count = pCount;
}
@@ -3988,16 +3989,14 @@ public final class ActivityThread {
buf.append(": ");
buf.append(cpi.name);
Log.i(TAG, buf.toString());
IContentProvider cp = installProvider(context, null, cpi, false);
IContentProvider cp = installProvider(context, null, cpi,
false /*noisy*/, true /*noReleaseNeeded*/);
if (cp != null) {
IActivityManager.ContentProviderHolder cph =
new IActivityManager.ContentProviderHolder(cpi);
new IActivityManager.ContentProviderHolder(cpi);
cph.provider = cp;
cph.noReleaseNeeded = true;
results.add(cph);
// Don't ever unload this provider from the process.
synchronized(mProviderMap) {
mProviderRefCountMap.put(cp.asBinder(), new ProviderRefCount(10000));
}
}
}
@@ -4008,26 +4007,22 @@ public final class ActivityThread {
}
}
private IContentProvider getExistingProvider(Context context, String name) {
synchronized(mProviderMap) {
final ProviderClientRecord pr = mProviderMap.get(name);
if (pr != null) {
return pr.mProvider;
}
return null;
}
}
private IContentProvider getProvider(Context context, String name) {
IContentProvider existing = getExistingProvider(context, name);
if (existing != null) {
return existing;
public final IContentProvider acquireProvider(Context c, String name) {
IContentProvider provider = acquireExistingProvider(c, name);
if (provider != null) {
return provider;
}
// There is a possible race here. Another thread may try to acquire
// the same provider at the same time. When this happens, we want to ensure
// that the first one wins.
// Note that we cannot hold the lock while acquiring and installing the
// provider since it might take a long time to run and it could also potentially
// be re-entrant in the case where the provider is in the same process.
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
getApplicationThread(), name);
getApplicationThread(), name);
} catch (RemoteException ex) {
}
if (holder == null) {
@@ -4035,136 +4030,137 @@ public final class ActivityThread {
return null;
}
IContentProvider prov = installProvider(context, holder.provider,
holder.info, true);
//Slog.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded);
if (holder.noReleaseNeeded || holder.provider == null) {
// We are not going to release the provider if it is an external
// provider that doesn't care about being released, or if it is
// a local provider running in this process.
//Slog.i(TAG, "*** NO RELEASE NEEDED");
synchronized(mProviderMap) {
mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000));
// Install provider will increment the reference count for us, and break
// any ties in the race.
provider = installProvider(c, holder.provider, holder.info,
true /*noisy*/, holder.noReleaseNeeded);
if (holder.provider != null && provider != holder.provider) {
if (localLOGV) {
Slog.v(TAG, "acquireProvider: lost the race, releasing extraneous "
+ "reference to the content provider");
}
try {
ActivityManagerNative.getDefault().removeContentProvider(
getApplicationThread(), name);
} catch (RemoteException ex) {
}
}
return prov;
}
public final IContentProvider acquireProvider(Context c, String name) {
IContentProvider provider = getProvider(c, name);
if(provider == null)
return null;
IBinder jBinder = provider.asBinder();
synchronized(mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if(prc == null) {
mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
} else {
prc.count++;
} //end else
} //end synchronized
return provider;
}
public final IContentProvider acquireExistingProvider(Context c, String name) {
IContentProvider provider = getExistingProvider(c, name);
if(provider == null)
return null;
IBinder jBinder = provider.asBinder();
synchronized(mProviderMap) {
synchronized (mProviderMap) {
ProviderClientRecord pr = mProviderMap.get(name);
if (pr == null) {
return null;
}
IContentProvider provider = pr.mProvider;
IBinder jBinder = provider.asBinder();
// Only increment the ref count if we have one. If we don't then the
// provider is not reference counted and never needs to be released.
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if(prc == null) {
mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
} else {
prc.count++;
} //end else
} //end synchronized
return provider;
if (prc != null) {
prc.count += 1;
if (prc.count == 1) {
if (localLOGV) {
Slog.v(TAG, "acquireExistingProvider: "
+ "snatched provider from the jaws of death");
}
// Because the provider previously had a reference count of zero,
// it was scheduled to be removed. Cancel that.
mH.removeMessages(H.REMOVE_PROVIDER, provider);
}
}
return provider;
}
}
public final boolean releaseProvider(IContentProvider provider) {
if(provider == null) {
return false;
}
IBinder jBinder = provider.asBinder();
synchronized(mProviderMap) {
synchronized (mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if(prc == null) {
if(localLOGV) Slog.v(TAG, "releaseProvider::Weird shouldn't be here");
if (prc == null) {
// The provider has no ref count, no release is needed.
return false;
} else {
prc.count--;
if(prc.count == 0) {
// Schedule the actual remove asynchronously, since we
// don't know the context this will be called in.
// TODO: it would be nice to post a delayed message, so
// if we come back and need the same provider quickly
// we will still have it available.
Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider);
mH.sendMessage(msg);
} //end if
} //end else
} //end synchronized
return true;
}
if (prc.count == 0) {
if (localLOGV) Slog.v(TAG, "releaseProvider: ref count already 0, how?");
return false;
}
prc.count -= 1;
if (prc.count == 0) {
// Schedule the actual remove asynchronously, since we don't know the context
// this will be called in.
// TODO: it would be nice to post a delayed message, so
// if we come back and need the same provider quickly
// we will still have it available.
Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider);
mH.sendMessage(msg);
}
return true;
}
}
final void completeRemoveProvider(IContentProvider provider) {
IBinder jBinder = provider.asBinder();
String name = null;
String remoteProviderName = null;
synchronized(mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if(prc != null && prc.count == 0) {
mProviderRefCountMap.remove(jBinder);
//invoke removeProvider to dereference provider
name = removeProviderLocked(provider);
if (prc == null) {
// Either no release is needed (so we shouldn't be here) or the
// provider was already released.
if (localLOGV) Slog.v(TAG, "completeRemoveProvider: release not needed");
return;
}
if (prc.count != 0) {
// There was a race! Some other client managed to acquire
// the provider before the removal was completed.
// Abort the removal. We will do it later.
if (localLOGV) Slog.v(TAG, "completeRemoveProvider: lost the race, "
+ "provider still in use");
return;
}
mProviderRefCountMap.remove(jBinder);
Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator();
while (iter.hasNext()) {
ProviderClientRecord pr = iter.next();
IBinder myBinder = pr.mProvider.asBinder();
if (myBinder == jBinder) {
iter.remove();
if (pr.mLocalProvider == null) {
myBinder.unlinkToDeath(pr, 0);
if (remoteProviderName == null) {
remoteProviderName = pr.mName;
}
}
}
}
}
if (name != null) {
if (remoteProviderName != null) {
try {
if(localLOGV) Slog.v(TAG, "removeProvider::Invoking " +
"ActivityManagerNative.removeContentProvider(" + name);
if (localLOGV) {
Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative."
+ "removeContentProvider(" + remoteProviderName + ")");
}
ActivityManagerNative.getDefault().removeContentProvider(
getApplicationThread(), name);
getApplicationThread(), remoteProviderName);
} catch (RemoteException e) {
//do nothing content provider object is dead any way
} //end catch
}
}
}
public final String removeProviderLocked(IContentProvider provider) {
if (provider == null) {
return null;
}
IBinder providerBinder = provider.asBinder();
String name = null;
// remove the provider from mProviderMap
Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator();
while (iter.hasNext()) {
ProviderClientRecord pr = iter.next();
IBinder myBinder = pr.mProvider.asBinder();
if (myBinder == providerBinder) {
//find if its published by this process itself
if(pr.mLocalProvider != null) {
if(localLOGV) Slog.i(TAG, "removeProvider::found local provider returning");
return name;
}
if(localLOGV) Slog.v(TAG, "removeProvider::Not local provider Unlinking " +
"death recipient");
//content provider is in another process
myBinder.unlinkToDeath(pr, 0);
iter.remove();
//invoke remove only once for the very first name seen
if(name == null) {
name = pr.mName;
}
} //end if myBinder
} //end while iter
return name;
}
final void removeDeadProvider(String name, IContentProvider provider) {
synchronized(mProviderMap) {
@@ -4179,8 +4175,23 @@ public final class ActivityThread {
}
}
/**
* Installs the provider.
*
* Providers that are local to the process or that come from the system server
* may be installed permanently which is indicated by setting noReleaseNeeded to true.
* Other remote providers are reference counted. The initial reference count
* for all reference counted providers is one. Providers that are not reference
* counted do not have a reference count (at all).
*
* This method detects when a provider has already been installed. When this happens,
* it increments the reference count of the existing provider (if appropriate)
* and returns the existing provider. This can happen due to concurrent
* attempts to acquire the same provider.
*/
private IContentProvider installProvider(Context context,
IContentProvider provider, ProviderInfo info, boolean noisy) {
IContentProvider provider, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded) {
ContentProvider localProvider = null;
if (provider == null) {
if (noisy) {
@@ -4238,24 +4249,69 @@ public final class ActivityThread {
}
synchronized (mProviderMap) {
// Cache the pointer for the remote provider.
// There is a possibility that this thread raced with another thread to
// add the provider. If we find another thread got there first then we
// just get out of the way and return the original provider.
IBinder jBinder = provider.asBinder();
String names[] = PATTERN_SEMICOLON.split(info.authority);
for (int i=0; i<names.length; i++) {
ProviderClientRecord pr = new ProviderClientRecord(names[i], provider,
localProvider);
try {
provider.asBinder().linkToDeath(pr, 0);
for (int i = 0; i < names.length; i++) {
ProviderClientRecord pr = mProviderMap.get(names[i]);
if (pr != null) {
if (localLOGV) {
Slog.v(TAG, "installProvider: lost the race, "
+ "using existing named provider");
}
provider = pr.mProvider;
} else {
pr = new ProviderClientRecord(names[i], provider, localProvider);
if (localProvider == null) {
try {
jBinder.linkToDeath(pr, 0);
} catch (RemoteException e) {
// Provider already dead. Bail out of here without making
// any changes to the provider map or other data structures.
return null;
}
}
mProviderMap.put(names[i], pr);
} catch (RemoteException e) {
return null;
}
}
if (localProvider != null) {
mLocalProviders.put(provider.asBinder(),
new ProviderClientRecord(null, provider, localProvider));
ProviderClientRecord pr = mLocalProviders.get(jBinder);
if (pr != null) {
if (localLOGV) {
Slog.v(TAG, "installProvider: lost the race, "
+ "using existing local provider");
}
provider = pr.mProvider;
} else {
pr = new ProviderClientRecord(null, provider, localProvider);
mLocalProviders.put(jBinder, pr);
}
}
if (!noReleaseNeeded) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
if (prc != null) {
if (localLOGV) {
Slog.v(TAG, "installProvider: lost the race, incrementing ref count");
}
prc.count += 1;
if (prc.count == 1) {
if (localLOGV) {
Slog.v(TAG, "installProvider: "
+ "snatched provider from the jaws of death");
}
// Because the provider previously had a reference count of zero,
// it was scheduled to be removed. Cancel that.
mH.removeMessages(H.REMOVE_PROVIDER, provider);
}
} else {
mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
}
}
}
return provider;
}