Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a4db1b72 authored by Jeff Brown's avatar Jeff Brown Committed by Android Git Automerger
Browse files

am 642de92b: am 9058435d: Merge "Fix races when content providers are acquired...

am 642de92b: am 9058435d: Merge "Fix races when content providers are acquired and released." into ics-mr1

* commit '642de92b':
  Fix races when content providers are acquired and released.
parents aaba2529 642de92b
Loading
Loading
Loading
Loading
+190 −134
Original line number Diff line number Diff line
@@ -2734,8 +2734,9 @@ public final class ActivityThread {
        CharSequence description;
    }

    private class ProviderRefCount {
    private static final class ProviderRefCount {
        public int count;

        ProviderRefCount(int pCount) {
            count = pCount;
        }
@@ -3988,16 +3989,14 @@ public final class ActivityThread {
            buf.append(": ");
            buf.append(cpi.name);
            Log.i(TAG, buf.toString());
            IContentProvider cp = installProvider(context, null, cpi, false);
            IContentProvider cp = installProvider(context, null, cpi,
                    false /*noisy*/, true /*noReleaseNeeded*/);
            if (cp != null) {
                IActivityManager.ContentProviderHolder cph =
                        new IActivityManager.ContentProviderHolder(cpi);
                cph.provider = cp;
                cph.noReleaseNeeded = true;
                results.add(cph);
                // Don't ever unload this provider from the process.
                synchronized(mProviderMap) {
                    mProviderRefCountMap.put(cp.asBinder(), new ProviderRefCount(10000));
                }
            }
        }

@@ -4008,22 +4007,18 @@ public final class ActivityThread {
        }
    }

    private IContentProvider getExistingProvider(Context context, String name) {
        synchronized(mProviderMap) {
            final ProviderClientRecord pr = mProviderMap.get(name);
            if (pr != null) {
                return pr.mProvider;
            }
            return null;
        }
    }

    private IContentProvider getProvider(Context context, String name) {
        IContentProvider existing = getExistingProvider(context, name);
        if (existing != null) {
            return existing;
    public final IContentProvider acquireProvider(Context c, String name) {
        IContentProvider provider = acquireExistingProvider(c, name);
        if (provider != null) {
            return provider;
        }

        // There is a possible race here.  Another thread may try to acquire
        // the same provider at the same time.  When this happens, we want to ensure
        // that the first one wins.
        // Note that we cannot hold the lock while acquiring and installing the
        // provider since it might take a long time to run and it could also potentially
        // be re-entrant in the case where the provider is in the same process.
        IActivityManager.ContentProviderHolder holder = null;
        try {
            holder = ActivityManagerNative.getDefault().getContentProvider(
@@ -4035,135 +4030,136 @@ public final class ActivityThread {
            return null;
        }

        IContentProvider prov = installProvider(context, holder.provider,
                holder.info, true);
        //Slog.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded);
        if (holder.noReleaseNeeded || holder.provider == null) {
            // We are not going to release the provider if it is an external
            // provider that doesn't care about being released, or if it is
            // a local provider running in this process.
            //Slog.i(TAG, "*** NO RELEASE NEEDED");
            synchronized(mProviderMap) {
                mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000));
        // Install provider will increment the reference count for us, and break
        // any ties in the race.
        provider = installProvider(c, holder.provider, holder.info,
                true /*noisy*/, holder.noReleaseNeeded);
        if (holder.provider != null && provider != holder.provider) {
            if (localLOGV) {
                Slog.v(TAG, "acquireProvider: lost the race, releasing extraneous "
                        + "reference to the content provider");
            }
            try {
                ActivityManagerNative.getDefault().removeContentProvider(
                        getApplicationThread(), name);
            } catch (RemoteException ex) {
            }
        return prov;
        }

    public final IContentProvider acquireProvider(Context c, String name) {
        IContentProvider provider = getProvider(c, name);
        if(provider == null)
            return null;
        IBinder jBinder = provider.asBinder();
        synchronized(mProviderMap) {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if(prc == null) {
                mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
            } else {
                prc.count++;
            } //end else
        } //end synchronized
        return provider;
    }

    public final IContentProvider acquireExistingProvider(Context c, String name) {
        IContentProvider provider = getExistingProvider(c, name);
        if(provider == null)
        synchronized (mProviderMap) {
            ProviderClientRecord pr = mProviderMap.get(name);
            if (pr == null) {
                return null;
            }

            IContentProvider provider = pr.mProvider;
            IBinder jBinder = provider.asBinder();
        synchronized(mProviderMap) {

            // Only increment the ref count if we have one.  If we don't then the
            // provider is not reference counted and never needs to be released.
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if(prc == null) {
                mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
            } else {
                prc.count++;
            } //end else
        } //end synchronized
            if (prc != null) {
                prc.count += 1;
                if (prc.count == 1) {
                    if (localLOGV) {
                        Slog.v(TAG, "acquireExistingProvider: "
                                + "snatched provider from the jaws of death");
                    }
                    // Because the provider previously had a reference count of zero,
                    // it was scheduled to be removed.  Cancel that.
                    mH.removeMessages(H.REMOVE_PROVIDER, provider);
                }
            }
            return provider;
        }
    }

    public final boolean releaseProvider(IContentProvider provider) {
        if(provider == null) {
            return false;
        }

        IBinder jBinder = provider.asBinder();
        synchronized (mProviderMap) {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc == null) {
                if(localLOGV) Slog.v(TAG, "releaseProvider::Weird shouldn't be here");
                // The provider has no ref count, no release is needed.
                return false;
            } else {
                prc.count--;
            }

            if (prc.count == 0) {
                if (localLOGV) Slog.v(TAG, "releaseProvider: ref count already 0, how?");
                return false;
            }

            prc.count -= 1;
            if (prc.count == 0) {
                    // Schedule the actual remove asynchronously, since we
                    // don't know the context this will be called in.
                // Schedule the actual remove asynchronously, since we don't know the context
                // this will be called in.
                // TODO: it would be nice to post a delayed message, so
                // if we come back and need the same provider quickly
                // we will still have it available.
                Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider);
                mH.sendMessage(msg);
                } //end if
            } //end else
        } //end synchronized
            }
            return true;
        }
    }

    final void completeRemoveProvider(IContentProvider provider) {
        IBinder jBinder = provider.asBinder();
        String name = null;
        String remoteProviderName = null;
        synchronized(mProviderMap) {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if(prc != null && prc.count == 0) {
                mProviderRefCountMap.remove(jBinder);
                //invoke removeProvider to dereference provider
                name = removeProviderLocked(provider);
            }
        }
        
        if (name != null) {
            try {
                if(localLOGV) Slog.v(TAG, "removeProvider::Invoking " +
                        "ActivityManagerNative.removeContentProvider(" + name);
                ActivityManagerNative.getDefault().removeContentProvider(
                        getApplicationThread(), name);
            } catch (RemoteException e) {
                //do nothing content provider object is dead any way
            } //end catch
        }
            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;
            }

    public final String removeProviderLocked(IContentProvider provider) {
        if (provider == null) {
            return null;
            if (prc.count != 0) {
                // There was a race!  Some other client managed to acquire
                // the provider before the removal was completed.
                // Abort the removal.  We will do it later.
                if (localLOGV) Slog.v(TAG, "completeRemoveProvider: lost the race, "
                        + "provider still in use");
                return;
            }
        IBinder providerBinder = provider.asBinder();

        String name = null;
            mProviderRefCountMap.remove(jBinder);

        // remove the provider from mProviderMap
            Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator();
            while (iter.hasNext()) {
                ProviderClientRecord pr = iter.next();
                IBinder myBinder = pr.mProvider.asBinder();
            if (myBinder == providerBinder) {
                //find if its published by this process itself
                if(pr.mLocalProvider != null) {
                    if(localLOGV) Slog.i(TAG, "removeProvider::found local provider returning");
                    return name;
                }
                if(localLOGV) Slog.v(TAG, "removeProvider::Not local provider Unlinking " +
                        "death recipient");
                //content provider is in another process
                myBinder.unlinkToDeath(pr, 0);
                if (myBinder == jBinder) {
                    iter.remove();
                //invoke remove only once for the very first name seen
                if(name == null) {
                    name = pr.mName;
                    if (pr.mLocalProvider == null) {
                        myBinder.unlinkToDeath(pr, 0);
                        if (remoteProviderName == null) {
                            remoteProviderName = pr.mName;
                        }
                    }
                }
            }
        }
            } //end if myBinder
        }  //end while iter

        return name;
        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
            }
        }
    }

    final void removeDeadProvider(String name, IContentProvider provider) {
@@ -4179,8 +4175,23 @@ public final class ActivityThread {
        }
    }

    /**
     * Installs the provider.
     *
     * Providers that are local to the process or that come from the system server
     * may be installed permanently which is indicated by setting noReleaseNeeded to true.
     * Other remote providers are reference counted.  The initial reference count
     * for all reference counted providers is one.  Providers that are not reference
     * counted do not have a reference count (at all).
     *
     * This method detects when a provider has already been installed.  When this happens,
     * it increments the reference count of the existing provider (if appropriate)
     * and returns the existing provider.  This can happen due to concurrent
     * attempts to acquire the same provider.
     */
    private IContentProvider installProvider(Context context,
            IContentProvider provider, ProviderInfo info, boolean noisy) {
            IContentProvider provider, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded) {
        ContentProvider localProvider = null;
        if (provider == null) {
            if (noisy) {
@@ -4238,24 +4249,69 @@ public final class ActivityThread {
        }

        synchronized (mProviderMap) {
            // Cache the pointer for the remote provider.
            // There is a possibility that this thread raced with another thread to
            // add the provider.  If we find another thread got there first then we
            // just get out of the way and return the original provider.
            IBinder jBinder = provider.asBinder();
            String names[] = PATTERN_SEMICOLON.split(info.authority);
            for (int i = 0; i < names.length; i++) {
                ProviderClientRecord pr = new ProviderClientRecord(names[i], provider,
                        localProvider);
                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 {
                    provider.asBinder().linkToDeath(pr, 0);
                    mProviderMap.put(names[i], pr);
                            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) {
                mLocalProviders.put(provider.asBinder(),
                        new ProviderClientRecord(null, provider, localProvider));
                ProviderClientRecord pr = mLocalProviders.get(jBinder);
                if (pr != null) {
                    if (localLOGV) {
                        Slog.v(TAG, "installProvider: lost the race, "
                                + "using existing local provider");
                    }
                    provider = pr.mProvider;
                } else {
                    pr = new ProviderClientRecord(null, provider, localProvider);
                    mLocalProviders.put(jBinder, pr);
                }
            }

            if (!noReleaseNeeded) {
                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                if (prc != null) {
                    if (localLOGV) {
                        Slog.v(TAG, "installProvider: lost the race, incrementing ref count");
                    }
                    prc.count += 1;
                    if (prc.count == 1) {
                        if (localLOGV) {
                            Slog.v(TAG, "installProvider: "
                                    + "snatched provider from the jaws of death");
                        }
                        // Because the provider previously had a reference count of zero,
                        // it was scheduled to be removed.  Cancel that.
                        mH.removeMessages(H.REMOVE_PROVIDER, provider);
                    }
                } else {
                    mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
                }
            }
        }
        return provider;
    }