Loading core/java/android/app/ContextImpl.java +16 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; Loading Loading @@ -88,11 +89,12 @@ import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { Loading Loading @@ -212,13 +214,24 @@ class ContextImpl extends Context { static final int STATE_UNINITIALIZED = 0; static final int STATE_INITIALIZING = 1; static final int STATE_READY = 2; static final int STATE_NOT_FOUND = 3; /** @hide */ @IntDef(prefix = { "STATE_" }, value = { STATE_UNINITIALIZED, STATE_INITIALIZING, STATE_READY, STATE_NOT_FOUND, }) @Retention(RetentionPolicy.SOURCE) @interface ServiceInitializationState {} /** * Initialization state for each service. Any of {@link #STATE_UNINITIALIZED}, * {@link #STATE_INITIALIZING} or {@link #STATE_READY}, */ final AtomicInteger[] mServiceInitializationStateArray = SystemServiceRegistry.createServiceInitializationStateArray(); @ServiceInitializationState final int[] mServiceInitializationStateArray = new int[mServiceCache.length]; static ContextImpl getImpl(Context context) { Context nextContext; Loading core/java/android/app/SystemServiceRegistry.java +55 −78 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.app; import android.accounts.AccountManager; import android.accounts.IAccountManager; import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.job.IJobScheduler; Loading Loading @@ -160,7 +161,6 @@ import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; /** * Manages all of the system services that can be returned by {@link Context#getSystemService}. Loading Loading @@ -993,10 +993,6 @@ final class SystemServiceRegistry { return new Object[sServiceCacheSize]; } public static AtomicInteger[] createServiceInitializationStateArray() { return new AtomicInteger[sServiceCacheSize]; } /** * Gets a system service from a given context. */ Loading Loading @@ -1037,7 +1033,10 @@ final class SystemServiceRegistry { static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> { private final int mCacheIndex; public CachedServiceFetcher() { CachedServiceFetcher() { // Note this class must be instantiated only by the static initializer of the // outer class (SystemServiceRegistry), which already does the synchronization, // so bare access to sServiceCacheSize is okay here. mCacheIndex = sServiceCacheSize++; } Loading @@ -1045,95 +1044,73 @@ final class SystemServiceRegistry { @SuppressWarnings("unchecked") public final T getService(ContextImpl ctx) { final Object[] cache = ctx.mServiceCache; final int[] gates = ctx.mServiceInitializationStateArray; // Fast path. If it's already cached, just return it. Object service = cache[mCacheIndex]; if (service != null) { return (T) service; for (;;) { boolean doInitialize = false; synchronized (cache) { // Return it if we already have a cached instance. T service = (T) cache[mCacheIndex]; if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) { return service; } // Slow path. final AtomicInteger[] gates = ctx.mServiceInitializationStateArray; final AtomicInteger gate; // If we get here, there's no cached instance. synchronized (cache) { // See if it's cached or not again, with the lock held this time. service = cache[mCacheIndex]; if (service != null) { return (T) service; // Grr... if gate is STATE_READY, then this means we initialized the service // once but someone cleared it. // We start over from STATE_UNINITIALIZED. if (gates[mCacheIndex] == ContextImpl.STATE_READY) { gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED; } // Not initialized yet. Create an atomic boolean to control which thread should // instantiate the service. if (gates[mCacheIndex] != null) { gate = gates[mCacheIndex]; } else { gate = new AtomicInteger(ContextImpl.STATE_UNINITIALIZED); gates[mCacheIndex] = gate; // It's possible for multiple threads to get here at the same time, so // use the "gate" to make sure only the first thread will call createService(). // At this point, the gate must be either UNINITIALIZED or INITIALIZING. if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) { doInitialize = true; gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING; } } // Not cached yet. // // Note multiple threads can reach here for the same service on the same context // concurrently. // // Now we're going to instantiate the service, but do so without the cache held; // otherwise it could deadlock. (b/71882178) // // However we still don't want to instantiate the same service multiple times, so // use the atomic integer to ensure only one thread will call createService(). if (gate.compareAndSet( ContextImpl.STATE_UNINITIALIZED, ContextImpl.STATE_INITIALIZING)) { if (doInitialize) { // Only the first thread gets here. T service = null; @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND; try { // This thread is the first one to get here. Instantiate the service // *without* the cache lock held. try { service = createService(ctx); newState = ContextImpl.STATE_READY; synchronized (cache) { cache[mCacheIndex] = service; } } catch (ServiceNotFoundException e) { onServiceNotFound(e); } } finally { // Tell the all other threads that the cache is ready now. // (But it's still be null in case of ServiceNotFoundException.) synchronized (gate) { gate.set(ContextImpl.STATE_READY); gate.notifyAll(); synchronized (cache) { cache[mCacheIndex] = service; gates[mCacheIndex] = newState; cache.notifyAll(); } } return (T) service; return service; } // Other threads will wait on the gate lock. synchronized (gate) { boolean interrupted = false; // Note: We check whether "state == STATE_READY", not // "cache[mCacheIndex] != null", because "cache[mCacheIndex] == null" // is still a valid outcome in the ServiceNotFoundException case. while (gate.get() != ContextImpl.STATE_READY) { // The other threads will wait for the first thread to call notifyAll(), // and go back to the top and retry. synchronized (cache) { while (gates[mCacheIndex] < ContextImpl.STATE_READY) { try { gate.wait(); cache.wait(); } catch (InterruptedException e) { Log.w(TAG, "getService() interrupted"); interrupted = true; } } if (interrupted) { Thread.currentThread().interrupt(); return null; } } } // Now the first thread has initialized it. // It may still be null if ServiceNotFoundException was thrown, but that shouldn't // happen, so we'll just return null here in that case. synchronized (cache) { service = cache[mCacheIndex]; } return (T) service; } public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException; Loading Loading
core/java/android/app/ContextImpl.java +16 −3 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; Loading Loading @@ -88,11 +89,12 @@ import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { Loading Loading @@ -212,13 +214,24 @@ class ContextImpl extends Context { static final int STATE_UNINITIALIZED = 0; static final int STATE_INITIALIZING = 1; static final int STATE_READY = 2; static final int STATE_NOT_FOUND = 3; /** @hide */ @IntDef(prefix = { "STATE_" }, value = { STATE_UNINITIALIZED, STATE_INITIALIZING, STATE_READY, STATE_NOT_FOUND, }) @Retention(RetentionPolicy.SOURCE) @interface ServiceInitializationState {} /** * Initialization state for each service. Any of {@link #STATE_UNINITIALIZED}, * {@link #STATE_INITIALIZING} or {@link #STATE_READY}, */ final AtomicInteger[] mServiceInitializationStateArray = SystemServiceRegistry.createServiceInitializationStateArray(); @ServiceInitializationState final int[] mServiceInitializationStateArray = new int[mServiceCache.length]; static ContextImpl getImpl(Context context) { Context nextContext; Loading
core/java/android/app/SystemServiceRegistry.java +55 −78 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.app; import android.accounts.AccountManager; import android.accounts.IAccountManager; import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; import android.app.job.IJobScheduler; Loading Loading @@ -160,7 +161,6 @@ import com.android.internal.os.IDropBoxManagerService; import com.android.internal.policy.PhoneLayoutInflater; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; /** * Manages all of the system services that can be returned by {@link Context#getSystemService}. Loading Loading @@ -993,10 +993,6 @@ final class SystemServiceRegistry { return new Object[sServiceCacheSize]; } public static AtomicInteger[] createServiceInitializationStateArray() { return new AtomicInteger[sServiceCacheSize]; } /** * Gets a system service from a given context. */ Loading Loading @@ -1037,7 +1033,10 @@ final class SystemServiceRegistry { static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> { private final int mCacheIndex; public CachedServiceFetcher() { CachedServiceFetcher() { // Note this class must be instantiated only by the static initializer of the // outer class (SystemServiceRegistry), which already does the synchronization, // so bare access to sServiceCacheSize is okay here. mCacheIndex = sServiceCacheSize++; } Loading @@ -1045,95 +1044,73 @@ final class SystemServiceRegistry { @SuppressWarnings("unchecked") public final T getService(ContextImpl ctx) { final Object[] cache = ctx.mServiceCache; final int[] gates = ctx.mServiceInitializationStateArray; // Fast path. If it's already cached, just return it. Object service = cache[mCacheIndex]; if (service != null) { return (T) service; for (;;) { boolean doInitialize = false; synchronized (cache) { // Return it if we already have a cached instance. T service = (T) cache[mCacheIndex]; if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) { return service; } // Slow path. final AtomicInteger[] gates = ctx.mServiceInitializationStateArray; final AtomicInteger gate; // If we get here, there's no cached instance. synchronized (cache) { // See if it's cached or not again, with the lock held this time. service = cache[mCacheIndex]; if (service != null) { return (T) service; // Grr... if gate is STATE_READY, then this means we initialized the service // once but someone cleared it. // We start over from STATE_UNINITIALIZED. if (gates[mCacheIndex] == ContextImpl.STATE_READY) { gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED; } // Not initialized yet. Create an atomic boolean to control which thread should // instantiate the service. if (gates[mCacheIndex] != null) { gate = gates[mCacheIndex]; } else { gate = new AtomicInteger(ContextImpl.STATE_UNINITIALIZED); gates[mCacheIndex] = gate; // It's possible for multiple threads to get here at the same time, so // use the "gate" to make sure only the first thread will call createService(). // At this point, the gate must be either UNINITIALIZED or INITIALIZING. if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) { doInitialize = true; gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING; } } // Not cached yet. // // Note multiple threads can reach here for the same service on the same context // concurrently. // // Now we're going to instantiate the service, but do so without the cache held; // otherwise it could deadlock. (b/71882178) // // However we still don't want to instantiate the same service multiple times, so // use the atomic integer to ensure only one thread will call createService(). if (gate.compareAndSet( ContextImpl.STATE_UNINITIALIZED, ContextImpl.STATE_INITIALIZING)) { if (doInitialize) { // Only the first thread gets here. T service = null; @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND; try { // This thread is the first one to get here. Instantiate the service // *without* the cache lock held. try { service = createService(ctx); newState = ContextImpl.STATE_READY; synchronized (cache) { cache[mCacheIndex] = service; } } catch (ServiceNotFoundException e) { onServiceNotFound(e); } } finally { // Tell the all other threads that the cache is ready now. // (But it's still be null in case of ServiceNotFoundException.) synchronized (gate) { gate.set(ContextImpl.STATE_READY); gate.notifyAll(); synchronized (cache) { cache[mCacheIndex] = service; gates[mCacheIndex] = newState; cache.notifyAll(); } } return (T) service; return service; } // Other threads will wait on the gate lock. synchronized (gate) { boolean interrupted = false; // Note: We check whether "state == STATE_READY", not // "cache[mCacheIndex] != null", because "cache[mCacheIndex] == null" // is still a valid outcome in the ServiceNotFoundException case. while (gate.get() != ContextImpl.STATE_READY) { // The other threads will wait for the first thread to call notifyAll(), // and go back to the top and retry. synchronized (cache) { while (gates[mCacheIndex] < ContextImpl.STATE_READY) { try { gate.wait(); cache.wait(); } catch (InterruptedException e) { Log.w(TAG, "getService() interrupted"); interrupted = true; } } if (interrupted) { Thread.currentThread().interrupt(); return null; } } } // Now the first thread has initialized it. // It may still be null if ServiceNotFoundException was thrown, but that shouldn't // happen, so we'll just return null here in that case. synchronized (cache) { service = cache[mCacheIndex]; } return (T) service; } public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException; Loading