Merge "Avoid service wrapper initialization deadlock." into pi-dev
am: 1e36211a96
Change-Id: I1413c9f37c6ae189292def463e1621983e2f87f2
This commit is contained in:
@@ -92,6 +92,7 @@ import java.nio.ByteOrder;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
class ReceiverRestrictedContext extends ContextWrapper {
|
class ReceiverRestrictedContext extends ContextWrapper {
|
||||||
ReceiverRestrictedContext(Context base) {
|
ReceiverRestrictedContext(Context base) {
|
||||||
@@ -208,6 +209,17 @@ class ContextImpl extends Context {
|
|||||||
// The system service cache for the system services that are cached per-ContextImpl.
|
// The system service cache for the system services that are cached per-ContextImpl.
|
||||||
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
|
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
|
||||||
|
|
||||||
|
static final int STATE_UNINITIALIZED = 0;
|
||||||
|
static final int STATE_INITIALIZING = 1;
|
||||||
|
static final int STATE_READY = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialization state for each service. Any of {@link #STATE_UNINITIALIZED},
|
||||||
|
* {@link #STATE_INITIALIZING} or {@link #STATE_READY},
|
||||||
|
*/
|
||||||
|
final AtomicInteger[] mServiceInitializationStateArray =
|
||||||
|
SystemServiceRegistry.createServiceInitializationStateArray();
|
||||||
|
|
||||||
static ContextImpl getImpl(Context context) {
|
static ContextImpl getImpl(Context context) {
|
||||||
Context nextContext;
|
Context nextContext;
|
||||||
while ((context instanceof ContextWrapper) &&
|
while ((context instanceof ContextWrapper) &&
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ import com.android.internal.os.IDropBoxManagerService;
|
|||||||
import com.android.internal.policy.PhoneLayoutInflater;
|
import com.android.internal.policy.PhoneLayoutInflater;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages all of the system services that can be returned by {@link Context#getSystemService}.
|
* Manages all of the system services that can be returned by {@link Context#getSystemService}.
|
||||||
@@ -992,6 +993,10 @@ final class SystemServiceRegistry {
|
|||||||
return new Object[sServiceCacheSize];
|
return new Object[sServiceCacheSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AtomicInteger[] createServiceInitializationStateArray() {
|
||||||
|
return new AtomicInteger[sServiceCacheSize];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a system service from a given context.
|
* Gets a system service from a given context.
|
||||||
*/
|
*/
|
||||||
@@ -1040,19 +1045,95 @@ final class SystemServiceRegistry {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public final T getService(ContextImpl ctx) {
|
public final T getService(ContextImpl ctx) {
|
||||||
final Object[] cache = ctx.mServiceCache;
|
final Object[] cache = ctx.mServiceCache;
|
||||||
|
|
||||||
|
// Fast path. If it's already cached, just return it.
|
||||||
|
Object service = cache[mCacheIndex];
|
||||||
|
if (service != null) {
|
||||||
|
return (T) service;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slow path.
|
||||||
|
final AtomicInteger[] gates = ctx.mServiceInitializationStateArray;
|
||||||
|
final AtomicInteger gate;
|
||||||
|
|
||||||
synchronized (cache) {
|
synchronized (cache) {
|
||||||
// Fetch or create the service.
|
// See if it's cached or not again, with the lock held this time.
|
||||||
Object service = cache[mCacheIndex];
|
service = cache[mCacheIndex];
|
||||||
if (service == null) {
|
if (service != null) {
|
||||||
|
return (T) service;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
try {
|
try {
|
||||||
service = createService(ctx);
|
service = createService(ctx);
|
||||||
cache[mCacheIndex] = service;
|
|
||||||
|
synchronized (cache) {
|
||||||
|
cache[mCacheIndex] = service;
|
||||||
|
}
|
||||||
} catch (ServiceNotFoundException e) {
|
} catch (ServiceNotFoundException e) {
|
||||||
onServiceNotFound(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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
|
||||||
|
|||||||
Reference in New Issue
Block a user