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

Commit bda1b8f6 authored by Christian Wailes's avatar Christian Wailes Committed by Android (Google) Code Review
Browse files

Merge changes Ib8f3b5b0,I66b01930,I560cdf9c

* changes:
  Teaches the System Server to enable the Zygote's blastula pool.
  Added pool management code around zygote state changes.
  Improved file descriptor cleanup in Zygote.
parents 5624e4f0 db132a3e
Loading
Loading
Loading
Loading
+100 −40
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.Zygote;
import com.android.internal.util.Preconditions;

import java.io.BufferedWriter;
import java.io.DataInputStream;
@@ -122,8 +121,9 @@ public class ZygoteProcess {
                new LocalSocketAddress(Zygote.BLASTULA_POOL_SECONDARY_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);

        // TODO (chriswailes): Uncomment when the blastula pool can be enabled.
//        fetchBlastulaPoolEnabledProp();
        if (fetchBlastulaPoolEnabledProp()) {
            informZygotesOfBlastulaPoolStatus();
        }
    }

    public ZygoteProcess(LocalSocketAddress primarySocketAddress,
@@ -325,12 +325,9 @@ public class ZygoteProcess {
                                                  @Nullable String sandboxId,
                                                  boolean useBlastulaPool,
                                                  @Nullable String[] zygoteArgs) {
        if (fetchBlastulaPoolEnabledProp()) {
            // TODO (chriswailes): Send the appropriate command to the zygotes
            Log.i(LOG_TAG, "Blastula pool enabled property set to: " + mBlastulaPoolEnabled);

            // This can't be enabled yet, but we do want to test this code path.
            mBlastulaPoolEnabled = false;
        // TODO (chriswailes): Is there a better place to check this value?
        if (fetchBlastulaPoolEnabledPropWithMinInterval()) {
            informZygotesOfBlastulaPoolStatus();
        }

        try {
@@ -385,7 +382,7 @@ public class ZygoteProcess {
     * @throws ZygoteStartFailedEx if process start failed for any reason
     */
    @GuardedBy("mLock")
    private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
    private Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args)
            throws ZygoteStartFailedEx {
        // Throw early if any of the arguments are malformed. This means we can
@@ -413,7 +410,7 @@ public class ZygoteProcess {
        Process.ProcessStartResult result = new Process.ProcessStartResult();

        // TODO (chriswailes): Move branch body into separate function.
        if (useBlastulaPool && isValidBlastulaCommand(args)) {
        if (useBlastulaPool && mBlastulaPoolEnabled && isValidBlastulaCommand(args)) {
            LocalSocket blastulaSessionSocket = null;

            try {
@@ -442,7 +439,7 @@ public class ZygoteProcess {
                // If there was an IOException using the blastula pool we will log the error and
                // attempt to start the process through the Zygote.
                Log.e(LOG_TAG, "IO Exception while communicating with blastula pool - "
                               + ex.toString());
                               + ex.getMessage());
            } finally {
                try {
                    blastulaSessionSocket.close();
@@ -648,7 +645,7 @@ public class ZygoteProcess {

        synchronized(mLock) {
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                              useBlastulaPool && mBlastulaPoolEnabled,
                                              useBlastulaPool,
                                              argsForZygote);
        }
    }
@@ -668,6 +665,10 @@ public class ZygoteProcess {
                            Boolean.parseBoolean(BLASTULA_POOL_ENABLED_DEFAULT));
        }

        if (origVal != mBlastulaPoolEnabled) {
            Log.i(LOG_TAG, "blastulaPoolEnabled = " + mBlastulaPoolEnabled);
        }

        return origVal != mBlastulaPoolEnabled;
    }

@@ -830,50 +831,58 @@ public class ZygoteProcess {
    }

    /**
     * Tries to open a session socket to a Zygote process with a compatible ABI if one is not
     * already open. If a compatible session socket is already open that session socket is returned.
     * This function may block and may have to try connecting to multiple Zygotes to find the
     * appropriate one.  Requires that mLock be held.
     * Creates a ZygoteState for the primary zygote if it doesn't exist or has been disconnected.
     */
    @GuardedBy("mLock")
    private ZygoteState openZygoteSocketIfNeeded(String abi)
            throws ZygoteStartFailedEx {

        Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");

    private void attemptConnectionToPrimaryZygote() throws IOException {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
            primaryZygoteState =
                    ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }

            maybeSetApiBlacklistExemptions(primaryZygoteState, false);
            maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
        }

        if (primaryZygoteState.matches(abi)) {
            return primaryZygoteState;
    }

        // The primary zygote didn't match. Try the secondary.
    /**
     * Creates a ZygoteState for the secondary zygote if it doesn't exist or has been disconnected.
     */
    @GuardedBy("mLock")
    private void attemptConnectionToSecondaryZygote() throws IOException {
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
            secondaryZygoteState =
                    ZygoteState.connect(mZygoteSecondarySocketAddress,
                            mBlastulaPoolSecondarySocketAddress);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }

            maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
            maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
        }
    }

    /**
     * Tries to open a session socket to a Zygote process with a compatible ABI if one is not
     * already open. If a compatible session socket is already open that session socket is returned.
     * This function may block and may have to try connecting to multiple Zygotes to find the
     * appropriate one.  Requires that mLock be held.
     */
    @GuardedBy("mLock")
    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        try {
            attemptConnectionToPrimaryZygote();

            if (primaryZygoteState.matches(abi)) {
                return primaryZygoteState;
            }

            // The primary zygote didn't match. Try the secondary.
            attemptConnectionToSecondaryZygote();

            if (secondaryZygoteState.matches(abi)) {
                return secondaryZygoteState;
            }
        } catch (IOException ioe) {
            throw new ZygoteStartFailedEx("Error connecting to zygote", ioe);
        }

        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }
@@ -997,6 +1006,57 @@ public class ZygoteProcess {
                + zygoteSocketAddress.getName());
    }

    /**
     * Sends messages to the zygotes telling them to change the status of their blastula pools.  If
     * this notification fails the ZygoteProcess will fall back to the previous behavior.
     */
    private void informZygotesOfBlastulaPoolStatus() {
        final String command = "1\n--blastula-pool-enabled=" + mBlastulaPoolEnabled + "\n";

        synchronized (mLock) {
            try {
                attemptConnectionToPrimaryZygote();

                primaryZygoteState.mZygoteOutputWriter.write(command);
                primaryZygoteState.mZygoteOutputWriter.flush();
            } catch (IOException ioe) {
                mBlastulaPoolEnabled = !mBlastulaPoolEnabled;
                Log.w(LOG_TAG, "Failed to inform zygotes of blastula pool status: "
                        + ioe.getMessage());
                return;
            }

            try {
                attemptConnectionToSecondaryZygote();

                try {
                    secondaryZygoteState.mZygoteOutputWriter.write(command);
                    secondaryZygoteState.mZygoteOutputWriter.flush();

                    // Wait for the secondary Zygote to finish its work.
                    secondaryZygoteState.mZygoteInputStream.readInt();
                } catch (IOException ioe) {
                    throw new IllegalStateException(
                            "Blastula pool state change cause an irrecoverable error",
                            ioe);
                }
            } catch (IOException ioe) {
                // No secondary zygote present.  This is expected on some devices.
            }

            // Wait for the response from the primary zygote here so the primary/secondary zygotes
            // can work concurrently.
            try {
                // Wait for the primary zygote to finish its work.
                primaryZygoteState.mZygoteInputStream.readInt();
            } catch (IOException ioe) {
                throw new IllegalStateException(
                        "Blastula pool state change cause an irrecoverable error",
                        ioe);
            }
        }
    }

    /**
     * Starts a new zygote process as a child of this zygote. This is used to create
     * secondary zygotes that inherit data from the zygote that this object
+18 −13
Original line number Diff line number Diff line
@@ -162,9 +162,9 @@ public final class Zygote {
    /**
     * The duration to wait before re-checking Zygote related system properties.
     *
     * Five minutes in milliseconds.
     * One minute in milliseconds.
     */
    public static final long PROPERTY_CHECK_INTERVAL = 300000;
    public static final long PROPERTY_CHECK_INTERVAL = 60000;

    /**
     * @hide for internal use only
@@ -427,6 +427,12 @@ public final class Zygote {
                defaultValue);
    }

    protected static void emptyBlastulaPool() {
        nativeEmptyBlastulaPool();
    }

    private static native void nativeEmptyBlastulaPool();

    /**
     * Returns the value of a system property converted to a boolean using specific logic.
     *
@@ -520,7 +526,7 @@ public final class Zygote {
        LocalSocket sessionSocket = null;
        DataOutputStream blastulaOutputStream = null;
        Credentials peerCredentials = null;
        String[] argStrings = null;
        ZygoteArguments args = null;

        while (true) {
            try {
@@ -533,25 +539,24 @@ public final class Zygote {

                peerCredentials = sessionSocket.getPeerCredentials();

                argStrings = readArgumentList(blastulaReader);
                String[] argStrings = readArgumentList(blastulaReader);

                if (argStrings != null) {
                    args = new ZygoteArguments(argStrings);

                    // TODO (chriswailes): Should this only be run for debug builds?
                    validateBlastulaCommand(args);
                    break;
                } else {
                    Log.e("Blastula", "Truncated command received.");
                    IoUtils.closeQuietly(sessionSocket);
                }
            } catch (IOException ioEx) {
                Log.e("Blastula", "Failed to read command: " + ioEx.getMessage());
            } catch (Exception ex) {
                Log.e("Blastula", ex.getMessage());
                IoUtils.closeQuietly(sessionSocket);
            }
        }

        ZygoteArguments args = new ZygoteArguments(argStrings);

        // TODO (chriswailes): Should this only be run for debug builds?
        validateBlastulaCommand(args);

        applyUidSecurityPolicy(args, peerCredentials);
        applyDebuggerSystemProperty(args);

+10 −0
Original line number Diff line number Diff line
@@ -100,6 +100,12 @@ class ZygoteArguments {
    boolean mSeInfoSpecified;
    String mSeInfo;

    /**
     *
     */
    boolean mBlastulaPoolEnabled;
    boolean mBlastulaPoolStatusSpecified = false;

    /**
     * from all --rlimit=r,c,m
     */
@@ -397,6 +403,10 @@ class ZygoteArguments {
                    throw new IllegalArgumentException("Duplicate arg specified");
                }
                mSandboxId = arg.substring(arg.indexOf('=') + 1);
            } else if (arg.startsWith("--blastula-pool-enabled=")) {
                mBlastulaPoolStatusSpecified = true;
                mBlastulaPoolEnabled = Boolean.parseBoolean(arg.substring(arg.indexOf('=') + 1));
                expectRuntimeArgs = false;
            } else {
                break;
            }
+80 −12
Original line number Diff line number Diff line
@@ -158,6 +158,10 @@ class ZygoteConnection {
            return null;
        }

        if (parsedArgs.mBlastulaPoolStatusSpecified) {
            return handleBlastulaPoolStatusChange(zygoteServer, parsedArgs.mBlastulaPoolEnabled);
        }

        if (parsedArgs.mPreloadDefault) {
            handlePreload();
            return null;
@@ -185,13 +189,12 @@ class ZygoteConnection {
        }

        if (parsedArgs.mApiBlacklistExemptions != null) {
            handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions);
            return null;
            return handleApiBlacklistExemptions(zygoteServer, parsedArgs.mApiBlacklistExemptions);
        }

        if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) {
            handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate);
            return null;
            return handleHiddenApiAccessLogSampleRate(zygoteServer,
                    parsedArgs.mHiddenApiAccessLogSampleRate);
        }

        if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) {
@@ -325,10 +328,64 @@ class ZygoteConnection {
        }
    }

    private void handleApiBlacklistExemptions(String[] exemptions) {
    private Runnable stateChangeWithBlastulaPoolReset(ZygoteServer zygoteServer,
            Runnable stateChangeCode) {
        try {
            ZygoteInit.setApiBlacklistExemptions(exemptions);
            if (zygoteServer.isBlastulaPoolEnabled()) {
                Zygote.emptyBlastulaPool();
            }

            stateChangeCode.run();

            if (zygoteServer.isBlastulaPoolEnabled()) {
                Runnable fpResult =
                        zygoteServer.fillBlastulaPool(
                                new int[]{mSocket.getFileDescriptor().getInt$()});

                if (fpResult != null) {
                    zygoteServer.setForkChild();
                    return fpResult;
                }
            }

            mSocketOutStream.writeInt(0);

            return null;
        } catch (IOException ioe) {
            throw new IllegalStateException("Error writing to command socket", ioe);
        }
    }

    /**
     * Makes the necessary changes to implement a new API blacklist exemption policy, and then
     * responds to the system server, letting it know that the task has been completed.
     *
     * This necessitates a change to the internal state of the Zygote.  As such, if the blastula
     * pool is enabled all existing blastulas have an incorrect API blacklist exemption list.  To
     * properly handle this request the pool must be emptied and refilled.  This process can return
     * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked.
     *
     * @param zygoteServer  The server object that received the request
     * @param exemptions  The new exemption list.
     * @return A Runnable object representing a new app in any blastulas spawned from here; the
     *         zygote process will always receive a null value from this function.
     */
    private Runnable handleApiBlacklistExemptions(ZygoteServer zygoteServer, String[] exemptions) {
        return stateChangeWithBlastulaPoolReset(zygoteServer,
                () -> ZygoteInit.setApiBlacklistExemptions(exemptions));
    }

    private Runnable handleBlastulaPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) {
        try {
            Runnable fpResult = zygoteServer.setBlastulaPoolStatus(newStatus, mSocket);

            if (fpResult == null) {
                mSocketOutStream.writeInt(0);
            } else {
                zygoteServer.setForkChild();
            }

            return fpResult;
        } catch (IOException ioe) {
            throw new IllegalStateException("Error writing to command socket", ioe);
        }
@@ -384,15 +441,26 @@ class ZygoteConnection {
        }
    }

    private void handleHiddenApiAccessLogSampleRate(int samplingRate) {
        try {
    /**
     * Changes the API access log sample rate for the Zygote and processes spawned from it.
     *
     * This necessitates a change to the internal state of the Zygote.  As such, if the blastula
     * pool is enabled all existing blastulas have an incorrect API access log sample rate.  To
     * properly handle this request the pool must be emptied and refilled.  This process can return
     * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked.
     *
     * @param zygoteServer  The server object that received the request
     * @param samplingRate  The new sample rate
     * @return A Runnable object representing a new app in any blastulas spawned from here; the
     *         zygote process will always receive a null value from this function.
     */
    private Runnable handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer,
            int samplingRate) {
        return stateChangeWithBlastulaPoolReset(zygoteServer, () -> {
            ZygoteInit.setHiddenApiAccessLogSampleRate(samplingRate);
            HiddenApiUsageLogger.setHiddenApiAccessLogSampleRate(samplingRate);
            ZygoteInit.setHiddenApiUsageLogger(HiddenApiUsageLogger.getInstance());
            mSocketOutStream.writeInt(0);
        } catch (IOException ioe) {
            throw new IllegalStateException("Error writing to command socket", ioe);
        }
        });
    }

    protected void preload() {
+139 −75
Original line number Diff line number Diff line
@@ -67,6 +67,15 @@ class ZygoteServer {
    /** The default value used for the BLASTULA_POOL_SIZE_MIN device property */
    private static final String BLASTULA_POOL_SIZE_MIN_DEFAULT = "1";

    /**
     * Indicates if this Zygote server can support a blastula pool.  Currently this should only be
     * true for the primary and secondary Zygotes, and not the App Zygotes or the WebView Zygote.
     *
     * TODO (chriswailes): Make this an explicit argument to the constructor
     */

    private final boolean mBlastulaPoolSupported;

    /**
     * If the blastula pool should be created and used to start applications.
     *
@@ -127,6 +136,8 @@ class ZygoteServer {
        mBlastulaPoolEventFD = null;
        mZygoteSocket = null;
        mBlastulaPoolSocket = null;

        mBlastulaPoolSupported = false;
    }

    /**
@@ -151,12 +162,18 @@ class ZygoteServer {
        }

        fetchBlastulaPoolPolicyProps();

        mBlastulaPoolSupported = true;
    }

    void setForkChild() {
        mIsForkChild = true;
    }

    public boolean isBlastulaPoolEnabled() {
        return mBlastulaPoolEnabled;
    }

    /**
     * Registers a server socket for zygote command connections. This opens the server socket
     * at the specified name in the abstract socket namespace.
@@ -224,6 +241,7 @@ class ZygoteServer {
    }

    private void fetchBlastulaPoolPolicyProps() {
        if (mBlastulaPoolSupported) {
            final String blastulaPoolSizeMaxPropString =
                    Zygote.getSystemProperty(
                            DeviceConfig.RuntimeNative.BLASTULA_POOL_SIZE_MAX,
@@ -259,7 +277,7 @@ class ZygoteServer {
                                Integer.parseInt(blastulaPoolRefillThresholdPropString),
                                mBlastulaPoolSizeMax);
            }

        }
    }

    private long mLastPropCheckTimestamp = 0;
@@ -282,8 +300,8 @@ class ZygoteServer {
     *         this function will return a Runnable object representing the new application that is
     *         passed up from blastulaMain.
     */
    private Runnable fillBlastulaPool(int[] sessionSocketRawFDs) {
        if (mBlastulaPoolEnabled) {

    Runnable fillBlastulaPool(int[] sessionSocketRawFDs) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool");

        int blastulaPoolCount = Zygote.getBlastulaPoolCount();
@@ -314,10 +332,31 @@ class ZygoteServer {
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        return null;
    }

    /**
     * Empty or fill the blastula pool as dictated by the current and new blastula pool statuses.
     */
    Runnable setBlastulaPoolStatus(boolean newStatus, LocalSocket sessionSocket) {
        if (!mBlastulaPoolSupported) {
            Log.w(TAG,
                    "Attempting to enable a blastula pool for a Zygote that doesn't support it.");
            return null;
        } else if (mBlastulaPoolEnabled == newStatus) {
            return null;
        }

        mBlastulaPoolEnabled = newStatus;

        if (newStatus) {
            return fillBlastulaPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() });
        } else {
            Zygote.emptyBlastulaPool();
            return null;
        }
    }

    /**
     * Runs the zygote process's select loop. Accepts new connections as
@@ -334,12 +373,26 @@ class ZygoteServer {
        while (true) {
            fetchBlastulaPoolPolicyPropsWithMinInterval();

            int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs();
            int[] blastulaPipeFDs = null;
            StructPollfd[] pollFDs = null;

            // Space for all of the socket FDs, the Blastula Pool Event FD, and
            // all of the open blastula read pipe FDs.
            StructPollfd[] pollFDs =
                new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length];
            // Allocate enough space for the poll structs, taking into account
            // the state of the blastula pool for this Zygote (could be a
            // regular Zygote, a WebView Zygote, or an AppZygote).
            if (mBlastulaPoolEnabled) {
                blastulaPipeFDs = Zygote.getBlastulaPipeFDs();
                pollFDs = new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length];
            } else {
                pollFDs = new StructPollfd[socketFDs.size()];
            }

            /*
             * For reasons of correctness the blastula pool pipe and event FDs
             * must be processed before the session and server sockets.  This
             * is to ensure that the blastula pool accounting information is
             * accurate when handling other requests like API blacklist
             * exemptions.
             */

            int pollIndex = 0;
            for (FileDescriptor socketFD : socketFDs) {
@@ -350,6 +403,8 @@ class ZygoteServer {
            }

            final int blastulaPoolEventFDIndex = pollIndex;

            if (mBlastulaPoolEnabled) {
                pollFDs[pollIndex] = new StructPollfd();
                pollFDs[pollIndex].fd = mBlastulaPoolEventFD;
                pollFDs[pollIndex].events = (short) POLLIN;
@@ -364,6 +419,7 @@ class ZygoteServer {
                    pollFDs[pollIndex].events = (short) POLLIN;
                    ++pollIndex;
                }
            }

            try {
                Os.poll(pollFDs, -1);
@@ -371,6 +427,8 @@ class ZygoteServer {
                throw new RuntimeException("poll failed", ex);
            }

            boolean blastulaPoolFDRead = false;

            while (--pollIndex >= 0) {
                if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
                    continue;
@@ -390,6 +448,7 @@ class ZygoteServer {
                        ZygoteConnection connection = peers.get(pollIndex);
                        final Runnable command = connection.processOneCommand(this);

                        // TODO (chriswailes): Is this extra check necessary?
                        if (mIsForkChild) {
                            // We're in the child. We should always have a command to run at this
                            // stage if processOneCommand hasn't called "exec".
@@ -480,6 +539,12 @@ class ZygoteServer {
                        Zygote.removeBlastulaTableEntry((int) messagePayload);
                    }

                    blastulaPoolFDRead = true;
                }
            }

            // Check to see if the blastula pool needs to be refilled.
            if (blastulaPoolFDRead) {
                int[] sessionSocketRawFDs =
                        socketFDs.subList(1, socketFDs.size())
                                .stream()
@@ -495,4 +560,3 @@ class ZygoteServer {
        }
    }
}
}
Loading