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

Commit 04954b39 authored by Makoto Onuki's avatar Makoto Onuki Committed by android-build-merger
Browse files

Merge "Simplify service wrapper initialization + make robust" into pi-dev am: 291988ef

am: 7c878a75

Change-Id: I024c14162d313a0e20c460b537f067732ee5e384
parents 40e83c55 7c878a75
Loading
Loading
Loading
Loading
+16 −3
Original line number Diff line number Diff line
@@ -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;
+55 −78
Original line number Diff line number Diff line
@@ -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;
                    }

                // 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;