diff options
author | Dianne Hackborn <hackbod@google.com> | 2012-05-29 15:57:52 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2012-05-29 15:57:52 -0700 |
commit | ce783749b191a58e4fed9a397066376915c1db65 (patch) | |
tree | fe7bbae9d9db880b5e69d82d96cdffa5d4040ed9 | |
parent | b042f2d9908e20852e4077878e50a0c07b8eee79 (diff) | |
parent | f3b4c93e0da9af2db9e16864faa734cf70fecfe3 (diff) | |
download | frameworks_base-ce783749b191a58e4fed9a397066376915c1db65.zip frameworks_base-ce783749b191a58e4fed9a397066376915c1db65.tar.gz frameworks_base-ce783749b191a58e4fed9a397066376915c1db65.tar.bz2 |
am f3b4c93e: am ae5811c7: Merge "Fix (mostly) issue #5109947: Race condition between retrieving a..." into jb-dev
* commit 'f3b4c93e0da9af2db9e16864faa734cf70fecfe3':
Fix (mostly) issue #5109947: Race condition between retrieving a...
16 files changed, 1178 insertions, 531 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 4506546..2ed93f4 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -580,7 +580,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder b = data.readStrongBinder(); IApplicationThread app = ApplicationThreadNative.asInterface(b); String name = data.readString(); - ContentProviderHolder cph = getContentProvider(app, name); + boolean stable = data.readInt() != 0; + ContentProviderHolder cph = getContentProvider(app, name, stable); reply.writeNoException(); if (cph != null) { reply.writeInt(1); @@ -617,12 +618,30 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case REF_CONTENT_PROVIDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + int stable = data.readInt(); + int unstable = data.readInt(); + boolean res = refContentProvider(b, stable, unstable); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case UNSTABLE_PROVIDER_DIED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + unstableProviderDied(b); + reply.writeNoException(); + return true; + } + case REMOVE_CONTENT_PROVIDER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); - IApplicationThread app = ApplicationThreadNative.asInterface(b); - String name = data.readString(); - removeContentProvider(app, name); + boolean stable = data.readInt() != 0; + removeContentProvider(b, stable); reply.writeNoException(); return true; } @@ -2314,13 +2333,13 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } public ContentProviderHolder getContentProvider(IApplicationThread caller, - String name) throws RemoteException - { + String name, boolean stable) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); data.writeString(name); + data.writeInt(stable ? 1 : 0); mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); reply.readException(); int res = reply.readInt(); @@ -2352,7 +2371,7 @@ class ActivityManagerProxy implements IActivityManager return cph; } public void publishContentProviders(IApplicationThread caller, - List<ContentProviderHolder> providers) throws RemoteException + List<ContentProviderHolder> providers) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2364,14 +2383,38 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - - public void removeContentProvider(IApplicationThread caller, - String name) throws RemoteException { + public boolean refContentProvider(IBinder connection, int stable, int unstable) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); - data.writeStrongBinder(caller != null ? caller.asBinder() : null); - data.writeString(name); + data.writeStrongBinder(connection); + data.writeInt(stable); + data.writeInt(unstable); + mRemote.transact(REF_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public void unstableProviderDied(IBinder connection) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(connection); + mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(connection); + data.writeInt(stable ? 1 : 0); mRemote.transact(REMOVE_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 33e639e..a457e3c 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -140,6 +140,7 @@ public final class ActivityThread { private static final boolean DEBUG_CONFIGURATION = false; private static final boolean DEBUG_SERVICE = false; private static final boolean DEBUG_MEMORY_TRIM = false; + private static final boolean DEBUG_PROVIDER = false; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; @@ -210,6 +211,8 @@ public final class ActivityThread { = new HashMap<IBinder, ProviderRefCount>(); final HashMap<IBinder, ProviderClientRecord> mLocalProviders = new HashMap<IBinder, ProviderClientRecord>(); + final HashMap<ComponentName, ProviderClientRecord> mLocalProvidersByName + = new HashMap<ComponentName, ProviderClientRecord>(); final HashMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners = new HashMap<Activity, ArrayList<OnActivityPausedListener>>(); @@ -284,20 +287,19 @@ public final class ActivityThread { } } - final class ProviderClientRecord implements IBinder.DeathRecipient { - final String mName; + final class ProviderClientRecord { + final String[] mNames; final IContentProvider mProvider; final ContentProvider mLocalProvider; + final IActivityManager.ContentProviderHolder mHolder; - ProviderClientRecord(String name, IContentProvider provider, - ContentProvider localProvider) { - mName = name; + ProviderClientRecord(String[] names, IContentProvider provider, + ContentProvider localProvider, + IActivityManager.ContentProviderHolder holder) { + mNames = names; mProvider = provider; mLocalProvider = localProvider; - } - - public void binderDied() { - removeDeadProvider(mName, mProvider); + mHolder = holder; } } @@ -1061,6 +1063,11 @@ public final class ActivityThread { pw.flush(); } + @Override + public void unstableProviderDied(IBinder provider) { + queueOrSendMessage(H.UNSTABLE_PROVIDER_DIED, provider); + } + private void printRow(PrintWriter pw, String format, Object...objs) { pw.println(String.format(format, objs)); } @@ -1125,6 +1132,7 @@ public final class ActivityThread { public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139; public static final int TRIM_MEMORY = 140; public static final int DUMP_PROVIDER = 141; + public static final int UNSTABLE_PROVIDER_DIED = 142; String codeToString(int code) { if (DEBUG_MESSAGES) { switch (code) { @@ -1170,6 +1178,7 @@ public final class ActivityThread { case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO"; case TRIM_MEMORY: return "TRIM_MEMORY"; case DUMP_PROVIDER: return "DUMP_PROVIDER"; + case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED"; } } return Integer.toString(code); @@ -1337,7 +1346,7 @@ public final class ActivityThread { break; case REMOVE_PROVIDER: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove"); - completeRemoveProvider((IContentProvider)msg.obj); + completeRemoveProvider((ProviderRefCount)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case ENABLE_JIT: @@ -1377,6 +1386,9 @@ public final class ActivityThread { handleTrimMemory(msg.arg1); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; + case UNSTABLE_PROVIDER_DIED: + handleUnstableProviderDied((IBinder)msg.obj, false); + break; } if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); } @@ -2867,10 +2879,24 @@ public final class ActivityThread { } private static final class ProviderRefCount { - public int count; + public final IActivityManager.ContentProviderHolder holder; + public final ProviderClientRecord client; + public int stableCount; + public int unstableCount; - ProviderRefCount(int pCount) { - count = pCount; + // When this is set, the stable and unstable ref counts are 0 and + // we have a pending operation scheduled to remove the ref count + // from the activity manager. On the activity manager we are still + // holding an unstable ref, though it is not reflected in the counts + // here. + public boolean removePending; + + ProviderRefCount(IActivityManager.ContentProviderHolder inHolder, + ProviderClientRecord inClient, int sCount, int uCount) { + holder = inHolder; + client = inClient; + stableCount = sCount; + unstableCount = uCount; } } @@ -4080,15 +4106,6 @@ public final class ActivityThread { Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); } - try { - mInstrumentation.onCreate(data.instrumentationArgs); - } - catch (Exception e) { - throw new RuntimeException( - "Exception thrown in onCreate() of " - + data.instrumentationName + ": " + e.toString(), e); - } - } else { mInstrumentation = new Instrumentation(); } @@ -4119,6 +4136,17 @@ public final class ActivityThread { } } + // Do this after providers, since instrumentation tests generally start their + // test thread at this point, and we don't want that racing. + try { + mInstrumentation.onCreate(data.instrumentationArgs); + } + catch (Exception e) { + throw new RuntimeException( + "Exception thrown in onCreate() of " + + data.instrumentationName + ": " + e.toString(), e); + } + try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { @@ -4159,12 +4187,9 @@ public final class ActivityThread { buf.append(": "); buf.append(cpi.name); Log.i(TAG, buf.toString()); - IContentProvider cp = installProvider(context, null, cpi, - false /*noisy*/, true /*noReleaseNeeded*/); - if (cp != null) { - IActivityManager.ContentProviderHolder cph = - new IActivityManager.ContentProviderHolder(cpi); - cph.provider = cp; + IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi, + false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/); + if (cph != null) { cph.noReleaseNeeded = true; results.add(cph); } @@ -4177,8 +4202,8 @@ public final class ActivityThread { } } - public final IContentProvider acquireProvider(Context c, String name) { - IContentProvider provider = acquireExistingProvider(c, name); + public final IContentProvider acquireProvider(Context c, String name, boolean stable) { + IContentProvider provider = acquireExistingProvider(c, name, stable); if (provider != null) { return provider; } @@ -4192,7 +4217,7 @@ public final class ActivityThread { IActivityManager.ContentProviderHolder holder = null; try { holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), name); + getApplicationThread(), name, stable); } catch (RemoteException ex) { } if (holder == null) { @@ -4202,23 +4227,79 @@ public final class ActivityThread { // 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"); + holder = installProvider(c, holder, holder.info, + true /*noisy*/, holder.noReleaseNeeded, stable); + return holder.provider; + } + + private final void incProviderRefLocked(ProviderRefCount prc, boolean stable) { + if (stable) { + prc.stableCount += 1; + if (prc.stableCount == 1) { + // We are acquiring a new stable reference on the provider. + int unstableDelta; + if (prc.removePending) { + // We have a pending remove operation, which is holding the + // last unstable reference. At this point we are converting + // that unstable reference to our new stable reference. + unstableDelta = -1; + // Cancel the removal of the provider. + if (DEBUG_PROVIDER) { + Slog.v(TAG, "incProviderRef: stable " + + "snatched provider from the jaws of death"); + } + prc.removePending = false; + mH.removeMessages(H.REMOVE_PROVIDER, prc); + } else { + unstableDelta = 0; + } + try { + if (DEBUG_PROVIDER) { + Slog.v(TAG, "incProviderRef Now stable - " + + prc.holder.info.name + ": unstableDelta=" + + unstableDelta); + } + ActivityManagerNative.getDefault().refContentProvider( + prc.holder.connection, 1, unstableDelta); + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } } - try { - ActivityManagerNative.getDefault().removeContentProvider( - getApplicationThread(), name); - } catch (RemoteException ex) { + } else { + prc.unstableCount += 1; + if (prc.unstableCount == 1) { + // We are acquiring a new unstable reference on the provider. + if (prc.removePending) { + // Oh look, we actually have a remove pending for the + // provider, which is still holding the last unstable + // reference. We just need to cancel that to take new + // ownership of the reference. + if (DEBUG_PROVIDER) { + Slog.v(TAG, "incProviderRef: unstable " + + "snatched provider from the jaws of death"); + } + prc.removePending = false; + mH.removeMessages(H.REMOVE_PROVIDER, prc); + } else { + // First unstable ref, increment our count in the + // activity manager. + try { + if (DEBUG_PROVIDER) { + Slog.v(TAG, "incProviderRef: Now unstable - " + + prc.holder.info.name); + } + ActivityManagerNative.getDefault().refContentProvider( + prc.holder.connection, 0, 1); + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } + } } } - return provider; } - public final IContentProvider acquireExistingProvider(Context c, String name) { + public final IContentProvider acquireExistingProvider(Context c, String name, + boolean stable) { synchronized (mProviderMap) { ProviderClientRecord pr = mProviderMap.get(name); if (pr == null) { @@ -4232,23 +4313,14 @@ public final class ActivityThread { // provider is not reference counted and never needs to be released. ProviderRefCount prc = mProviderRefCountMap.get(jBinder); 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); - } + incProviderRefLocked(prc, stable); } return provider; } } - public final boolean releaseProvider(IContentProvider provider) { - if(provider == null) { + public final boolean releaseProvider(IContentProvider provider, boolean stable) { + if (provider == null) { return false; } @@ -4260,55 +4332,98 @@ public final class ActivityThread { return false; } - if (prc.count == 0) { - if (localLOGV) Slog.v(TAG, "releaseProvider: ref count already 0, how?"); - return false; + boolean lastRef = false; + if (stable) { + if (prc.stableCount == 0) { + if (DEBUG_PROVIDER) Slog.v(TAG, + "releaseProvider: stable ref count already 0, how?"); + return false; + } + prc.stableCount -= 1; + if (prc.stableCount == 0) { + // What we do at this point depends on whether there are + // any unstable refs left: if there are, we just tell the + // activity manager to decrement its stable count; if there + // aren't, we need to enqueue this provider to be removed, + // and convert to holding a single unstable ref while + // doing so. + lastRef = prc.unstableCount == 0; + try { + if (DEBUG_PROVIDER) { + Slog.v(TAG, "releaseProvider: No longer stable w/lastRef=" + + lastRef + " - " + prc.holder.info.name); + } + ActivityManagerNative.getDefault().refContentProvider( + prc.holder.connection, -1, lastRef ? 1 : 0); + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } + } + } else { + if (prc.unstableCount == 0) { + if (DEBUG_PROVIDER) Slog.v(TAG, + "releaseProvider: unstable ref count already 0, how?"); + return false; + } + prc.unstableCount -= 1; + if (prc.unstableCount == 0) { + // If this is the last reference, we need to enqueue + // this provider to be removed instead of telling the + // activity manager to remove it at this point. + lastRef = prc.stableCount == 0; + if (!lastRef) { + try { + if (DEBUG_PROVIDER) { + Slog.v(TAG, "releaseProvider: No longer unstable - " + + prc.holder.info.name); + } + ActivityManagerNative.getDefault().refContentProvider( + prc.holder.connection, 0, -1); + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } + } + } } - 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); + if (lastRef) { + if (!prc.removePending) { + // 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. + if (DEBUG_PROVIDER) { + Slog.v(TAG, "releaseProvider: Enqueueing pending removal - " + + prc.holder.info.name); + } + prc.removePending = true; + Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, prc); + mH.sendMessage(msg); + } else { + Slog.w(TAG, "Duplicate remove pending of provider " + prc.holder.info.name); + } } return true; } } - public final IContentProvider acquireUnstableProvider(Context c, String name) { - return acquireProvider(c, name); - } - - public final boolean releaseUnstableProvider(IContentProvider provider) { - return releaseProvider(provider); - } - - final void completeRemoveProvider(IContentProvider provider) { - IBinder jBinder = provider.asBinder(); - String remoteProviderName = null; - synchronized(mProviderMap) { - ProviderRefCount prc = mProviderRefCountMap.get(jBinder); - 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) { + final void completeRemoveProvider(ProviderRefCount prc) { + synchronized (mProviderMap) { + if (!prc.removePending) { // 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, " + if (DEBUG_PROVIDER) Slog.v(TAG, "completeRemoveProvider: lost the race, " + "provider still in use"); return; } - mProviderRefCountMap.remove(jBinder); + final IBinder jBinder = prc.holder.provider.asBinder(); + ProviderRefCount existingPrc = mProviderRefCountMap.get(jBinder); + if (existingPrc == prc) { + mProviderRefCountMap.remove(jBinder); + } Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator(); while (iter.hasNext()) { @@ -4316,43 +4431,72 @@ public final class ActivityThread { 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 (remoteProviderName != null) { - try { - if (localLOGV) { - Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative." - + "removeContentProvider(" + remoteProviderName + ")"); - } - ActivityManagerNative.getDefault().removeContentProvider( - getApplicationThread(), remoteProviderName); - } catch (RemoteException e) { - //do nothing content provider object is dead any way + try { + if (DEBUG_PROVIDER) { + Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative." + + "removeContentProvider(" + prc.holder.info.name + ")"); } + ActivityManagerNative.getDefault().removeContentProvider( + prc.holder.connection, false); + } catch (RemoteException e) { + //do nothing content provider object is dead any way } } - final void removeDeadProvider(String name, IContentProvider provider) { + final void handleUnstableProviderDied(IBinder provider, boolean fromClient) { synchronized(mProviderMap) { - ProviderClientRecord pr = mProviderMap.get(name); - if (pr != null && pr.mProvider.asBinder() == provider.asBinder()) { - Slog.i(TAG, "Removing dead content provider: " + name); - ProviderClientRecord removed = mProviderMap.remove(name); - if (removed != null) { - removed.mProvider.asBinder().unlinkToDeath(removed, 0); + ProviderRefCount prc = mProviderRefCountMap.get(provider); + if (prc != null) { + if (DEBUG_PROVIDER) Slog.v(TAG, "Cleaning up dead provider " + + provider + " " + prc.holder.info.name); + mProviderRefCountMap.remove(provider); + if (prc.client != null && prc.client.mNames != null) { + for (String name : prc.client.mNames) { + ProviderClientRecord pr = mProviderMap.get(name); + if (pr != null && pr.mProvider.asBinder() == provider) { + Slog.i(TAG, "Removing dead content provider: " + name); + mProviderMap.remove(name); + } + } + } + if (fromClient) { + // We found out about this due to execution in our client + // code. Tell the activity manager about it now, to ensure + // that the next time we go to do anything with the provider + // it knows it is dead (so we don't race with its death + // notification). + try { + ActivityManagerNative.getDefault().unstableProviderDied( + prc.holder.connection); + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } } } } } + private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider, + ContentProvider localProvider,IActivityManager.ContentProviderHolder holder) { + String names[] = PATTERN_SEMICOLON.split(holder.info.authority); + ProviderClientRecord pcr = new ProviderClientRecord(names, provider, + localProvider, holder); + for (int i = 0; i < names.length; i++) { + ProviderClientRecord existing = mProviderMap.get(names[i]); + if (existing != null) { + Slog.w(TAG, "Content provider " + pcr.mHolder.info.name + + " already published as " + names[i]); + } else { + mProviderMap.put(names[i], pcr); + } + } + return pcr; + } + /** * Installs the provider. * @@ -4367,12 +4511,13 @@ public final class ActivityThread { * 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, boolean noReleaseNeeded) { + private IActivityManager.ContentProviderHolder installProvider(Context context, + IActivityManager.ContentProviderHolder holder, ProviderInfo info, + boolean noisy, boolean noReleaseNeeded, boolean stable) { ContentProvider localProvider = null; - if (provider == null) { - if (noisy) { + IContentProvider provider; + if (holder == null) { + if (DEBUG_PROVIDER || noisy) { Slog.d(TAG, "Loading provider " + info.authority + ": " + info.name); } @@ -4409,7 +4554,7 @@ public final class ActivityThread { info.applicationInfo.sourceDir); return null; } - if (false) Slog.v( + if (DEBUG_PROVIDER) Slog.v( TAG, "Instantiating local provider " + info.name); // XXX Need to create the correct context for this provider. localProvider.attachInfo(c, info); @@ -4421,76 +4566,72 @@ public final class ActivityThread { } return null; } - } else if (localLOGV) { - Slog.v(TAG, "Installing external provider " + info.authority + ": " + } else { + provider = holder.provider; + if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": " + info.name); } + IActivityManager.ContentProviderHolder retHolder; + synchronized (mProviderMap) { - // 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. + if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider + + " / " + info.name); IBinder jBinder = provider.asBinder(); - String names[] = PATTERN_SEMICOLON.split(info.authority); - 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); - } - } - if (localProvider != null) { - ProviderClientRecord pr = mLocalProviders.get(jBinder); + ComponentName cname = new ComponentName(info.packageName, info.name); + ProviderClientRecord pr = mLocalProvidersByName.get(cname); if (pr != null) { - if (localLOGV) { + if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, " + "using existing local provider"); } provider = pr.mProvider; } else { - pr = new ProviderClientRecord(null, provider, localProvider); + holder = new IActivityManager.ContentProviderHolder(info); + holder.provider = provider; + holder.noReleaseNeeded = true; + pr = installProviderAuthoritiesLocked(provider, localProvider, holder); mLocalProviders.put(jBinder, pr); + mLocalProvidersByName.put(cname, pr); } - } - - if (!noReleaseNeeded) { + retHolder = pr.mHolder; + } else { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); if (prc != null) { - if (localLOGV) { - Slog.v(TAG, "installProvider: lost the race, incrementing ref count"); + if (DEBUG_PROVIDER) { + Slog.v(TAG, "installProvider: lost the race, updating ref count"); } - prc.count += 1; - if (prc.count == 1) { - if (localLOGV) { - Slog.v(TAG, "installProvider: " - + "snatched provider from the jaws of death"); + // We need to transfer our new reference to the existing + // ref count, releasing the old one... but only if + // release is needed (that is, it is not running in the + // system process). + if (!noReleaseNeeded) { + incProviderRefLocked(prc, stable); + try { + ActivityManagerNative.getDefault().removeContentProvider( + holder.connection, stable); + } catch (RemoteException e) { + //do nothing content provider object is dead any way } - // 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)); + ProviderClientRecord client = installProviderAuthoritiesLocked( + provider, localProvider, holder); + if (noReleaseNeeded) { + prc = new ProviderRefCount(holder, client, 1000, 1000); + } else { + prc = stable + ? new ProviderRefCount(holder, client, 1, 0) + : new ProviderRefCount(holder, client, 0, 1); + } + mProviderRefCountMap.put(jBinder, prc); } + retHolder = prc.holder; } } - return provider; + + return retHolder; } private void attach(boolean system) { diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 437362b..3e726e0 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -575,6 +575,15 @@ public abstract class ApplicationThreadNative extends Binder reply.writeNoException(); return true; } + + case UNSTABLE_PROVIDER_DIED_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder provider = data.readStrongBinder(); + unstableProviderDied(provider); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -1163,4 +1172,12 @@ class ApplicationThreadProxy implements IApplicationThread { mRemote.transact(DUMP_DB_INFO_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } + + public void unstableProviderDied(IBinder provider) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(provider); + mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); + data.recycle(); + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 299e408..4c35a8c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1679,27 +1679,32 @@ class ContextImpl extends Context { @Override protected IContentProvider acquireProvider(Context context, String name) { - return mMainThread.acquireProvider(context, name); + return mMainThread.acquireProvider(context, name, true); } @Override protected IContentProvider acquireExistingProvider(Context context, String name) { - return mMainThread.acquireExistingProvider(context, name); + return mMainThread.acquireExistingProvider(context, name, true); } @Override public boolean releaseProvider(IContentProvider provider) { - return mMainThread.releaseProvider(provider); + return mMainThread.releaseProvider(provider, true); } @Override protected IContentProvider acquireUnstableProvider(Context c, String name) { - return mMainThread.acquireUnstableProvider(c, name); + return mMainThread.acquireProvider(c, name, false); } @Override public boolean releaseUnstableProvider(IContentProvider icp) { - return mMainThread.releaseUnstableProvider(icp); + return mMainThread.releaseProvider(icp, false); + } + + @Override + public void unstableProviderDied(IContentProvider icp) { + mMainThread.handleUnstableProviderDied(icp.asBinder(), true); } private final ActivityThread mMainThread; diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index cf304df..609a047 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -116,14 +116,16 @@ public interface IActivityManager extends IInterface { public void reportThumbnail(IBinder token, Bitmap thumbnail, CharSequence description) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, - String name) throws RemoteException; + String name, boolean stable) throws RemoteException; public ContentProviderHolder getContentProviderExternal(String name, IBinder token) throws RemoteException; - public void removeContentProvider(IApplicationThread caller, - String name) throws RemoteException; + public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException; public void removeContentProviderExternal(String name, IBinder token) throws RemoteException; public void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) throws RemoteException; + public boolean refContentProvider(IBinder connection, int stableDelta, int unstableDelta) + throws RemoteException; + public void unstableProviderDied(IBinder connection) throws RemoteException; public PendingIntent getRunningServiceControlPanel(ComponentName service) throws RemoteException; public ComponentName startService(IApplicationThread caller, Intent service, @@ -363,6 +365,7 @@ public interface IActivityManager extends IInterface { public static class ContentProviderHolder implements Parcelable { public final ProviderInfo info; public IContentProvider provider; + public IBinder connection; public boolean noReleaseNeeded; public ContentProviderHolder(ProviderInfo _info) { @@ -380,6 +383,7 @@ public interface IActivityManager extends IInterface { } else { dest.writeStrongBinder(null); } + dest.writeStrongBinder(connection); dest.writeInt(noReleaseNeeded ? 1:0); } @@ -398,6 +402,7 @@ public interface IActivityManager extends IInterface { info = ProviderInfo.CREATOR.createFromParcel(source); provider = ContentProviderNative.asInterface( source.readStrongBinder()); + connection = source.readStrongBinder(); noReleaseNeeded = source.readInt() != 0; } } @@ -476,7 +481,7 @@ public interface IActivityManager extends IInterface { int REPORT_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27; int GET_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; - + int REF_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; int FINISH_SUB_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; int GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; int START_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33; @@ -597,4 +602,5 @@ public interface IActivityManager extends IInterface { int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147; int FINISH_ACTIVITY_AFFINITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+148; int GET_LAUNCHED_FROM_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+149; + int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 70029d2..f60cfd6 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -128,6 +128,7 @@ public interface IApplicationThread extends IInterface { String[] args) throws RemoteException; void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException; void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException; + void unstableProviderDied(IBinder provider) throws RemoteException; String descriptor = "android.app.IApplicationThread"; @@ -176,4 +177,5 @@ public interface IApplicationThread extends IInterface { int DUMP_GFX_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43; int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44; int DUMP_DB_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45; + int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 75c6e11..cad4b01 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -987,7 +987,12 @@ public class Instrumentation { /** * Perform calling of the application's {@link Application#onCreate} * method. The default implementation simply calls through to that method. - * + * + * <p>Note: This method will be called immediately after {@link #onCreate(Bundle)}. + * Often instrumentation tests start their test thread in onCreate(); you + * need to be careful of races between these. (Well between it and + * everything else, but let's start here.) + * * @param app The application being created. */ public void callApplicationOnCreate(Application app) { diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 423f1f6..5c315ce 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -20,6 +20,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.DeadObjectException; import android.os.ICancellationSignal; import android.os.RemoteException; import android.os.ParcelFileDescriptor; @@ -33,11 +34,19 @@ import java.util.ArrayList; * calling {@link ContentResolver#acquireContentProviderClient}. This object must be released * using {@link #release} in order to indicate to the system that the {@link ContentProvider} is * no longer needed and can be killed to free up resources. + * + * <p>Note that you should generally create a new ContentProviderClient instance + * for each thread that will be performing operations. Unlike + * {@link ContentResolver}, the methods here such as {@link #query} and + * {@link #openFile} are not thread safe -- you must not call + * {@link #release()} on the ContentProviderClient those calls are made from + * until you are finished with the data they have returned. */ public class ContentProviderClient { private final IContentProvider mContentProvider; private final ContentResolver mContentResolver; private final boolean mStable; + private boolean mReleased; /** * @hide @@ -52,7 +61,14 @@ public class ContentProviderClient { /** See {@link ContentProvider#query ContentProvider.query} */ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException { - return query(url, projection, selection, selectionArgs, sortOrder, null); + try { + return query(url, projection, selection, selectionArgs, sortOrder, null); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#query ContentProvider.query} */ @@ -64,41 +80,90 @@ public class ContentProviderClient { remoteCancellationSignal = mContentProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } - return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder, - remoteCancellationSignal); + try { + return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder, + remoteCancellationSignal); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#getType ContentProvider.getType} */ public String getType(Uri url) throws RemoteException { - return mContentProvider.getType(url); + try { + return mContentProvider.getType(url); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */ public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { - return mContentProvider.getStreamTypes(url, mimeTypeFilter); + try { + return mContentProvider.getStreamTypes(url, mimeTypeFilter); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#insert ContentProvider.insert} */ public Uri insert(Uri url, ContentValues initialValues) throws RemoteException { - return mContentProvider.insert(url, initialValues); + try { + return mContentProvider.insert(url, initialValues); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */ public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException { - return mContentProvider.bulkInsert(url, initialValues); + try { + return mContentProvider.bulkInsert(url, initialValues); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#delete ContentProvider.delete} */ public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException { - return mContentProvider.delete(url, selection, selectionArgs); + try { + return mContentProvider.delete(url, selection, selectionArgs); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#update ContentProvider.update} */ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { - return mContentProvider.update(url, values, selection, selectionArgs); + try { + return mContentProvider.update(url, values, selection, selectionArgs); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** @@ -110,7 +175,14 @@ public class ContentProviderClient { */ public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException { - return mContentProvider.openFile(url, mode); + try { + return mContentProvider.openFile(url, mode); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** @@ -122,20 +194,41 @@ public class ContentProviderClient { */ public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, FileNotFoundException { - return mContentProvider.openAssetFile(url, mode); + try { + return mContentProvider.openAssetFile(url, mode); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, String mimeType, Bundle opts) throws RemoteException, FileNotFoundException { - return mContentProvider.openTypedAssetFile(uri, mimeType, opts); + try { + return mContentProvider.openTypedAssetFile(uri, mimeType, opts); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */ public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { - return mContentProvider.applyBatch(operations); + try { + return mContentProvider.applyBatch(operations); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } } /** @@ -144,10 +237,16 @@ public class ContentProviderClient { * @return true if this was release, false if it was already released */ public boolean release() { - if (mStable) { - return mContentResolver.releaseProvider(mContentProvider); - } else { - return mContentResolver.releaseUnstableProvider(mContentProvider); + synchronized (this) { + if (mReleased) { + throw new IllegalStateException("Already released"); + } + mReleased = true; + if (mStable) { + return mContentResolver.releaseProvider(mContentProvider); + } else { + return mContentResolver.releaseUnstableProvider(mContentProvider); + } } } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index f509fd8..34b5a30 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -20,27 +20,24 @@ import dalvik.system.CloseGuard; import android.accounts.Account; import android.app.ActivityManagerNative; -import android.app.ActivityThread; import android.app.AppGlobals; -import android.content.ContentProvider.Transport; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.database.ContentObserver; import android.database.CrossProcessCursorWrapper; import android.database.Cursor; -import android.database.CursorWrapper; import android.database.IContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.DeadObjectException; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StrictMode; import android.os.SystemClock; import android.text.TextUtils; import android.util.EventLog; @@ -202,6 +199,8 @@ public abstract class ContentResolver { protected abstract IContentProvider acquireUnstableProvider(Context c, String name); /** @hide */ public abstract boolean releaseUnstableProvider(IContentProvider icp); + /** @hide */ + public abstract void unstableProviderDied(IContentProvider icp); /** * Return the MIME type of the given content URL. @@ -211,6 +210,7 @@ public abstract class ContentResolver { * @return A MIME type for the content, or null if the URL is invalid or the type is unknown */ public final String getType(Uri url) { + // XXX would like to have an acquireExistingUnstableProvider for this. IContentProvider provider = acquireExistingProvider(url); if (provider != null) { try { @@ -351,23 +351,37 @@ public abstract class ContentResolver { public final Cursor query(final Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { - IContentProvider provider = acquireProvider(uri); - if (provider == null) { + IContentProvider unstableProvider = acquireUnstableProvider(uri); + if (unstableProvider == null) { return null; } + IContentProvider stableProvider = null; try { long startTime = SystemClock.uptimeMillis(); ICancellationSignal remoteCancellationSignal = null; if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); - remoteCancellationSignal = provider.createCancellationSignal(); + remoteCancellationSignal = unstableProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } - Cursor qCursor = provider.query(uri, projection, - selection, selectionArgs, sortOrder, remoteCancellationSignal); + Cursor qCursor; + try { + qCursor = unstableProvider.query(uri, projection, + selection, selectionArgs, sortOrder, remoteCancellationSignal); + } catch (DeadObjectException e) { + // The remote process has died... but we only hold an unstable + // reference though, so we might recover!!! Let's try!!!! + // This is exciting!!1!!1!!!!1 + unstableProviderDied(unstableProvider); + stableProvider = acquireProvider(uri); + if (stableProvider == null) { + return null; + } + qCursor = stableProvider.query(uri, projection, + selection, selectionArgs, sortOrder, remoteCancellationSignal); + } if (qCursor == null) { - releaseProvider(provider); return null; } // force query execution @@ -375,16 +389,21 @@ public abstract class ContentResolver { long durationMillis = SystemClock.uptimeMillis() - startTime; maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder); // Wrap the cursor object into CursorWrapperInner object - return new CursorWrapperInner(qCursor, provider); + CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, + stableProvider != null ? stableProvider : acquireProvider(uri)); + stableProvider = null; + return wrapper; } catch (RemoteException e) { - releaseProvider(provider); - // Arbitrary and not worth documenting, as Activity // Manager will kill this process shortly anyway. return null; - } catch (RuntimeException e) { - releaseProvider(provider); - throw e; + } finally { + if (unstableProvider != null) { + releaseUnstableProvider(unstableProvider); + } + if (stableProvider != null) { + releaseProvider(stableProvider); + } } } @@ -592,49 +611,63 @@ public abstract class ContentResolver { if ("r".equals(mode)) { return openTypedAssetFileDescriptor(uri, "*/*", null); } else { - int n = 0; - while (true) { - n++; - IContentProvider provider = acquireUnstableProvider(uri); - if (provider == null) { - throw new FileNotFoundException("No content provider: " + uri); - } + IContentProvider unstableProvider = acquireUnstableProvider(uri); + if (unstableProvider == null) { + throw new FileNotFoundException("No content provider: " + uri); + } + IContentProvider stableProvider = null; + AssetFileDescriptor fd = null; + + try { try { - AssetFileDescriptor fd = provider.openAssetFile(uri, mode); + fd = unstableProvider.openAssetFile(uri, mode); if (fd == null) { // The provider will be released by the finally{} clause return null; } - ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( - fd.getParcelFileDescriptor(), provider); - - // Success! Don't release the provider when exiting, let - // ParcelFileDescriptorInner do that when it is closed. - provider = null; - - return new AssetFileDescriptor(pfd, fd.getStartOffset(), - fd.getDeclaredLength()); - } catch (RemoteException e) { - // The provider died for some reason. Since we are - // acquiring it unstable, its process could have gotten - // killed and need to be restarted. We'll retry a couple - // times and if still can't succeed then fail. - if (n <= 2) { - try { - Thread.sleep(100); - } catch (InterruptedException e1) { - } - continue; + } catch (DeadObjectException e) { + // The remote process has died... but we only hold an unstable + // reference though, so we might recover!!! Let's try!!!! + // This is exciting!!1!!1!!!!1 + unstableProviderDied(unstableProvider); + stableProvider = acquireProvider(uri); + if (stableProvider == null) { + throw new FileNotFoundException("No content provider: " + uri); } - // Whatever, whatever, we'll go away. - throw new FileNotFoundException("Dead content provider: " + uri); - } catch (FileNotFoundException e) { - throw e; - } finally { - if (provider != null) { - releaseUnstableProvider(provider); + fd = stableProvider.openAssetFile(uri, mode); + if (fd == null) { + // The provider will be released by the finally{} clause + return null; } } + + if (stableProvider == null) { + stableProvider = acquireProvider(uri); + } + releaseUnstableProvider(unstableProvider); + ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( + fd.getParcelFileDescriptor(), stableProvider); + + // Success! Don't release the provider when exiting, let + // ParcelFileDescriptorInner do that when it is closed. + stableProvider = null; + + return new AssetFileDescriptor(pfd, fd.getStartOffset(), + fd.getDeclaredLength()); + + } catch (RemoteException e) { + // Whatever, whatever, we'll go away. + throw new FileNotFoundException( + "Failed opening content provider: " + uri); + } catch (FileNotFoundException e) { + throw e; + } finally { + if (stableProvider != null) { + releaseProvider(stableProvider); + } + if (unstableProvider != null) { + releaseUnstableProvider(unstableProvider); + } } } } @@ -670,49 +703,63 @@ public abstract class ContentResolver { */ public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, String mimeType, Bundle opts) throws FileNotFoundException { - int n = 0; - while (true) { - n++; - IContentProvider provider = acquireUnstableProvider(uri); - if (provider == null) { - throw new FileNotFoundException("No content provider: " + uri); - } + IContentProvider unstableProvider = acquireUnstableProvider(uri); + if (unstableProvider == null) { + throw new FileNotFoundException("No content provider: " + uri); + } + IContentProvider stableProvider = null; + AssetFileDescriptor fd = null; + + try { try { - AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts); + fd = unstableProvider.openTypedAssetFile(uri, mimeType, opts); if (fd == null) { // The provider will be released by the finally{} clause return null; } - ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( - fd.getParcelFileDescriptor(), provider); - - // Success! Don't release the provider when exiting, let - // ParcelFileDescriptorInner do that when it is closed. - provider = null; - - return new AssetFileDescriptor(pfd, fd.getStartOffset(), - fd.getDeclaredLength()); - } catch (RemoteException e) { - // The provider died for some reason. Since we are - // acquiring it unstable, its process could have gotten - // killed and need to be restarted. We'll retry a couple - // times and if still can't succeed then fail. - if (n <= 2) { - try { - Thread.sleep(100); - } catch (InterruptedException e1) { - } - continue; + } catch (DeadObjectException e) { + // The remote process has died... but we only hold an unstable + // reference though, so we might recover!!! Let's try!!!! + // This is exciting!!1!!1!!!!1 + unstableProviderDied(unstableProvider); + stableProvider = acquireProvider(uri); + if (stableProvider == null) { + throw new FileNotFoundException("No content provider: " + uri); } - // Whatever, whatever, we'll go away. - throw new FileNotFoundException("Dead content provider: " + uri); - } catch (FileNotFoundException e) { - throw e; - } finally { - if (provider != null) { - releaseUnstableProvider(provider); + fd = stableProvider.openTypedAssetFile(uri, mimeType, opts); + if (fd == null) { + // The provider will be released by the finally{} clause + return null; } } + + if (stableProvider == null) { + stableProvider = acquireProvider(uri); + } + releaseUnstableProvider(unstableProvider); + ParcelFileDescriptor pfd = new ParcelFileDescriptorInner( + fd.getParcelFileDescriptor(), stableProvider); + + // Success! Don't release the provider when exiting, let + // ParcelFileDescriptorInner do that when it is closed. + stableProvider = null; + + return new AssetFileDescriptor(pfd, fd.getStartOffset(), + fd.getDeclaredLength()); + + } catch (RemoteException e) { + // Whatever, whatever, we'll go away. + throw new FileNotFoundException( + "Failed opening content provider: " + uri); + } catch (FileNotFoundException e) { + throw e; + } finally { + if (stableProvider != null) { + releaseProvider(stableProvider); + } + if (unstableProvider != null) { + releaseUnstableProvider(unstableProvider); + } } } @@ -1061,7 +1108,7 @@ public abstract class ContentResolver { if (name == null) { return null; } - return acquireProvider(mContext, name); + return acquireUnstableProvider(mContext, name); } /** @@ -1113,10 +1160,15 @@ public abstract class ContentResolver { * use it as needed and it won't disappear, even if your process is in the * background. If using this method, you need to take care to deal with any * failures when communicating with the provider, and be sure to close it - * so that it can be re-opened later. + * so that it can be re-opened later. In particular, catching a + * {@link android.os.DeadObjectException} from the calls there will let you + * know that the content provider has gone away; at that point the current + * ContentProviderClient object is invalid, and you should release it. You + * can acquire a new one if you would like to try to restart the provider + * and perform new operations on it. */ public final ContentProviderClient acquireUnstableContentProviderClient(Uri uri) { - IContentProvider provider = acquireProvider(uri); + IContentProvider provider = acquireUnstableProvider(uri); if (provider != null) { return new ContentProviderClient(this, provider, false); } @@ -1133,10 +1185,15 @@ public abstract class ContentResolver { * use it as needed and it won't disappear, even if your process is in the * background. If using this method, you need to take care to deal with any * failures when communicating with the provider, and be sure to close it - * so that it can be re-opened later. + * so that it can be re-opened later. In particular, catching a + * {@link android.os.DeadObjectException} from the calls there will let you + * know that the content provider has gone away; at that point the current + * ContentProviderClient object is invalid, and you should release it. You + * can acquire a new one if you would like to try to restart the provider + * and perform new operations on it. */ public final ContentProviderClient acquireUnstableContentProviderClient(String name) { - IContentProvider provider = acquireProvider(name); + IContentProvider provider = acquireUnstableProvider(name); if (provider != null) { return new ContentProviderClient(this, provider, false); } @@ -1780,7 +1837,6 @@ public abstract class ContentResolver { private final class ParcelFileDescriptorInner extends ParcelFileDescriptor { private final IContentProvider mContentProvider; - public static final String TAG="ParcelFileDescriptorInner"; private boolean mReleaseProviderFlag = false; ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) { @@ -1792,7 +1848,7 @@ public abstract class ContentResolver { public void close() throws IOException { if(!mReleaseProviderFlag) { super.close(); - ContentResolver.this.releaseUnstableProvider(mContentProvider); + ContentResolver.this.releaseProvider(mContentProvider); mReleaseProviderFlag = true; } } 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) { diff --git a/test-runner/src/android/test/mock/MockContentResolver.java b/test-runner/src/android/test/mock/MockContentResolver.java index 65eb21b..715da0f 100644 --- a/test-runner/src/android/test/mock/MockContentResolver.java +++ b/test-runner/src/android/test/mock/MockContentResolver.java @@ -118,6 +118,11 @@ public class MockContentResolver extends ContentResolver { return releaseProvider(icp); } + /** @hide */ + @Override + public void unstableProviderDied(IContentProvider icp) { + } + /** * Overrides {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver, boolean) * ContentResolver.notifChange(Uri, ContentObserver, boolean)}. All parameters are ignored. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java index fec2c3f..8d259d7 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java @@ -72,6 +72,11 @@ public class BridgeContentResolver extends ContentResolver { return releaseProvider(icp); } + /** @hide */ + @Override + public void unstableProviderDied(IContentProvider icp) { + } + /** * Stub for the layoutlib bridge content resolver. */ |