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

Commit db132a3e authored by Chris Wailes's avatar Chris Wailes
Browse files

Teaches the System Server to enable the Zygote's blastula pool.

This patch adds the code necessary to communicate changes to the
blastula_pool_enabled system property from the  System Server to the
zygote process.  Checking this property asynchronously from both the
system server and the zygote can lead to a deadlock, so instead the
system server checks the property and notifies the zygote.

By default the blastula pool is disabled.

Test: adb shell device_config put runtime_native blastula_pool_enabled true
Test: adb shell device_config put runtime_native blastula_pool_enabled false
Change-Id: Ib8f3b5b0eb78349255b9b316d683a69747616ef7
parent 01060e66
Loading
Loading
Loading
Loading
+99 −39
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,
@@ -327,12 +327,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 {
@@ -387,7 +384,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
@@ -415,7 +412,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 {
@@ -665,7 +662,7 @@ public class ZygoteProcess {

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

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

        return origVal != mBlastulaPoolEnabled;
    }

@@ -847,50 +848,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);
    }
@@ -1014,6 +1023,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
+2 −2
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
+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
     */
@@ -402,6 +408,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;
            }
+20 −0
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;
@@ -371,6 +375,22 @@ class ZygoteConnection {
                () -> 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);
        }
    }

    private static class HiddenApiUsageLogger implements VMRuntime.HiddenApiUsageLogger {

        private final MetricsLogger mMetricsLogger = new MetricsLogger();
+55 −31
Original line number Diff line number Diff line
@@ -241,6 +241,7 @@ class ZygoteServer {
    }

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

        }
    }

    private long mLastPropCheckTimestamp = 0;
@@ -335,6 +336,28 @@ class ZygoteServer {
        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
     * they happen, and reads commands from connections one spawn-request's
@@ -425,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".
Loading