From 57307b21cbdb261eeca081051ed61540dcf905f8 Mon Sep 17 00:00:00 2001 From: riddle_hsu Date: Wed, 1 Apr 2015 15:06:18 +0800 Subject: [PATCH] [ActivityManager] Improve multi-thread access the same provider Application may use many threads to load data from provider. If the target provider needs to start process, each access will occupy one binder thread of system server until the provider process started and published. Sometimes application uses more than 16 threads to access the same provider, and the provider process needs a little long time to start, then all binder threads of system server are waiting. But when the provider is ready, it is unable to publish to notify those waiting threads because no availabe binder thread to use. And device will become almost hang. Improvement: If there is already a thread acquiring provider, let other threads (which try to acquire the same provider) wait the result of the first one. That reduces IPC to save binder thread of system server. Remove calling removeContentProvider in installProvider because we have ensured only get one provider holder for the same provider, the original race that gets a new useless holder will not happen. Change-Id: I521f2603db8ced56912f5dc54342a70451e68381 --- core/java/android/app/ActivityThread.java | 102 +++++++++++++++------- 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bdd9e41f23f70..beb244bc2b8be 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -257,18 +257,21 @@ public final class ActivityThread { } } + static final class AcquiringProviderRecord { + IActivityManager.ContentProviderHolder holder; + boolean acquiring = true; + int requests = 1; + } + // The lock of mProviderMap protects the following variables. - final ArrayMap mProviderMap - = new ArrayMap(); - final ArrayMap mProviderRefCountMap - = new ArrayMap(); - final ArrayMap mLocalProviders - = new ArrayMap(); - final ArrayMap mLocalProvidersByName - = new ArrayMap(); + final ArrayMap mProviderMap = new ArrayMap<>(); + final ArrayMap mAcquiringProviderMap = new ArrayMap<>(); + final ArrayMap mProviderRefCountMap = new ArrayMap<>(); + final ArrayMap mLocalProviders = new ArrayMap<>(); + final ArrayMap mLocalProvidersByName = new ArrayMap<>(); final ArrayMap> mOnPauseListeners - = new ArrayMap>(); + = new ArrayMap<>(); final GcIdler mGcIdler = new GcIdler(); boolean mGcIdlerScheduled = false; @@ -345,7 +348,7 @@ public final class ActivityThread { } } - final class ProviderClientRecord { + static final class ProviderClientRecord { final String[] mNames; final IContentProvider mProvider; final ContentProvider mLocalProvider; @@ -4648,22 +4651,57 @@ public final class ActivityThread { public final IContentProvider acquireProvider( Context c, String auth, int userId, boolean stable) { - final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable); + final ProviderKey key = new ProviderKey(auth, userId); + final IContentProvider provider = acquireExistingProvider(c, key, stable); if (provider != null) { return provider; } + AcquiringProviderRecord r; + boolean first = false; + synchronized (mAcquiringProviderMap) { + r = mAcquiringProviderMap.get(key); + if (r == null) { + r = new AcquiringProviderRecord(); + mAcquiringProviderMap.put(key, r); + first = true; + } else { + r.requests++; + } + } - // There is a possible race here. Another thread may try to acquire - // the same provider at the same time. When this happens, we want to ensure - // that the first one wins. - // Note that we cannot hold the lock while acquiring and installing the - // provider since it might take a long time to run and it could also potentially - // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; - try { - holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), auth, userId, stable); - } catch (RemoteException ex) { + if (first) { + // Multiple threads may try to acquire the same provider at the same time. + // When this happens, we only let the first one really gets provider. + // Other threads just wait for its result. + // 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. + try { + holder = ActivityManagerNative.getDefault().getContentProvider( + getApplicationThread(), auth, userId, stable); + } catch (RemoteException ex) { + } + synchronized (r) { + r.holder = holder; + r.acquiring = false; + r.notifyAll(); + } + } else { + synchronized (r) { + while (r.acquiring) { + try { + r.wait(); + } catch (InterruptedException e) { + } + } + holder = r.holder; + } + } + synchronized (mAcquiringProviderMap) { + if (--r.requests == 0) { + mAcquiringProviderMap.remove(key); + } } if (holder == null) { Slog.e(TAG, "Failed to find provider info for " + auth); @@ -4747,8 +4785,12 @@ public final class ActivityThread { public final IContentProvider acquireExistingProvider( Context c, String auth, int userId, boolean stable) { + return acquireExistingProvider(c, new ProviderKey(auth, userId), stable); + } + + final IContentProvider acquireExistingProvider( + Context c, ProviderKey key, boolean stable) { synchronized (mProviderMap) { - final ProviderKey key = new ProviderKey(auth, userId); final ProviderClientRecord pr = mProviderMap.get(key); if (pr == null) { return null; @@ -4759,7 +4801,7 @@ public final class ActivityThread { if (!jBinder.isBinderAlive()) { // The hosting process of the provider has died; we can't // use this one. - Log.i(TAG, "Acquiring provider " + auth + " for user " + userId + Log.i(TAG, "Acquiring provider " + key.authority + " for user " + key.userId + ": existing object's process dead"); handleUnstableProviderDiedLocked(jBinder, true); return null; @@ -5081,18 +5123,12 @@ public final class ActivityThread { if (DEBUG_PROVIDER) { Slog.v(TAG, "installProvider: lost the race, updating ref count"); } - // 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). + // The provider has already been installed, so we need + // to increase reference count to the existing one, but + // only if release is needed (that is, it is not running + // in the system process or local to the 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 - } } } else { ProviderClientRecord client = installProviderAuthoritiesLocked(