Merge "Simplify service wrapper initialization + make robust" into pi-dev
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.app;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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}.
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// 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 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;
|
||||
}
|
||||
}
|
||||
if (doInitialize) {
|
||||
// Only the first thread gets here.
|
||||
|
||||
// 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)) {
|
||||
try {
|
||||
// This thread is the first one to get here. Instantiate the service
|
||||
// *without* the cache lock held.
|
||||
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.
|
||||
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();
|
||||
}
|
||||
}
|
||||
return (T) 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) {
|
||||
try {
|
||||
gate.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "getService() interrupted");
|
||||
interrupted = true;
|
||||
} finally {
|
||||
synchronized (cache) {
|
||||
cache[mCacheIndex] = service;
|
||||
gates[mCacheIndex] = newState;
|
||||
cache.notifyAll();
|
||||
}
|
||||
}
|
||||
return service;
|
||||
}
|
||||
// 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 {
|
||||
cache.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "getService() interrupted");
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
|
||||
Reference in New Issue
Block a user