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

Commit ba4c2eb1 authored by Chris Wailes's avatar Chris Wailes Committed by Christian Wailes
Browse files

Enables the use of the blastula pool.

This commit adds the code necessar to initialize and use the blastula
pool during application launching.  Highlights include:
* Modifying ZygoteState to allow the creation of blastula session
sockets
* Modified application startup to track if a web view process is being
created.
* Initialization of the blastula pool during Zygote initialization.
* Blastula lifecycle management via reporting pipes and event FDs.
* Launching of applications via the blastula pool.

The creation, maintenance, and use of the blastula pool can be disabled
by setting Zygote.BLASTULA_POOL_ENABLED to false.  When this feature is
disabled applications will launch as they did before this patch.

Topic: zygote-prefork
Test: make & flash & launch app & check log message
Bug: 68253328
Exempt-From-Owner-Approval: No changes to files with owners between revisions
Change-Id: I46c32ad09400591e866b6c6121d5a9b0332092f3
parent 070aba8e
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -530,7 +530,7 @@ public class Process {
        return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, packageName,
                    packagesForUid, visibleVols, zygoteArgs);
                    packagesForUid, visibleVols, /*useBlastulaPool=*/ true, zygoteArgs);
    }

    /** @hide */
@@ -551,7 +551,7 @@ public class Process {
        return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, packageName,
                    packagesForUid, visibleVols, zygoteArgs);
                    packagesForUid, visibleVols, /*useBlastulaPool=*/ false, zygoteArgs);
    }

    /**
+165 −48
Original line number Diff line number Diff line
@@ -73,6 +73,16 @@ public class ZygoteProcess {
     */
    public static final String ZYGOTE_SECONDARY_SOCKET_NAME = "zygote_secondary";

    /**
     * @hide for internal use only
     */
    public static final String BLASTULA_POOL_SOCKET_NAME = "blastula_pool";

    /**
     * @hide for internal use only
     */
    public static final String BLASTULA_POOL_SECONDARY_SOCKET_NAME = "blastula_pool_secondary";

    /**
     * @hide for internal use only
     */
@@ -87,6 +97,15 @@ public class ZygoteProcess {
     * The name of the secondary (alternate ABI) zygote socket.
     */
    private final LocalSocketAddress mZygoteSecondarySocketAddress;
    /**
     * The name of the socket used to communicate with the primary blastula pool.
     */
    private final LocalSocketAddress mBlastulaPoolSocketAddress;

    /**
     * The name of the socket used to communicate with the secondary (alternate ABI) blastula pool.
     */
    private final LocalSocketAddress mBlastulaPoolSecondarySocketAddress;

    public ZygoteProcess() {
        mZygoteSocketAddress =
@@ -94,12 +113,22 @@ public class ZygoteProcess {
        mZygoteSecondarySocketAddress =
                new LocalSocketAddress(ZYGOTE_SECONDARY_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);

        mBlastulaPoolSocketAddress =
                new LocalSocketAddress(BLASTULA_POOL_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);
        mBlastulaPoolSecondarySocketAddress =
                new LocalSocketAddress(BLASTULA_POOL_SECONDARY_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);
    }

    public ZygoteProcess(LocalSocketAddress primarySocketAddress,
                         LocalSocketAddress secondarySocketAddress) {
        mZygoteSocketAddress = primarySocketAddress;
        mZygoteSecondarySocketAddress = secondarySocketAddress;

        mBlastulaPoolSocketAddress = null;
        mBlastulaPoolSecondarySocketAddress = null;
    }

    public LocalSocketAddress getPrimarySocketAddress() {
@@ -111,6 +140,7 @@ public class ZygoteProcess {
     */
    public static class ZygoteState {
        final LocalSocketAddress mZygoteSocketAddress;
        final LocalSocketAddress mBlastulaSocketAddress;

        private final LocalSocket mZygoteSessionSocket;

@@ -122,11 +152,13 @@ public class ZygoteProcess {
        private boolean mClosed;

        private ZygoteState(LocalSocketAddress zygoteSocketAddress,
                            LocalSocketAddress blastulaSocketAddress,
                            LocalSocket zygoteSessionSocket,
                            DataInputStream zygoteInputStream,
                            BufferedWriter zygoteOutputWriter,
                            List<String> abiList) {
            this.mZygoteSocketAddress = zygoteSocketAddress;
            this.mBlastulaSocketAddress = blastulaSocketAddress;
            this.mZygoteSessionSocket = zygoteSessionSocket;
            this.mZygoteInputStream = zygoteInputStream;
            this.mZygoteOutputWriter = zygoteOutputWriter;
@@ -134,14 +166,17 @@ public class ZygoteProcess {
        }

        /**
         * Create a new ZygoteState object by connecting to the given Zygote socket.
         * Create a new ZygoteState object by connecting to the given Zygote socket and saving the
         * given blastula socket address.
         *
         * @param zygoteSocketAddress  Zygote socket to connect to
         * @param blastulaSocketAddress  Blastula socket address to save for later
         * @return  A new ZygoteState object containing a session socket for the given Zygote socket
         * address
         * @throws IOException
         */
        public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress)
        public static ZygoteState connect(LocalSocketAddress zygoteSocketAddress,
                                          LocalSocketAddress blastulaSocketAddress)
                throws IOException {

            DataInputStream zygoteInputStream = null;
@@ -154,7 +189,7 @@ public class ZygoteProcess {
                zygoteOutputWriter =
                        new BufferedWriter(
                                new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
                                256);
                                Zygote.SOCKET_BUFFER_SIZE);
            } catch (IOException ex) {
                try {
                    zygoteSessionSocket.close();
@@ -163,11 +198,18 @@ public class ZygoteProcess {
                throw ex;
            }

            return new ZygoteState(zygoteSocketAddress,
            return new ZygoteState(zygoteSocketAddress, blastulaSocketAddress,
                                   zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter,
                                   getAbiList(zygoteOutputWriter, zygoteInputStream));
        }

        LocalSocket getBlastulaSessionSocket() throws IOException {
            final LocalSocket blastulaSessionSocket = new LocalSocket();
            blastulaSessionSocket.connect(this.mBlastulaSocketAddress);

            return blastulaSessionSocket;
        }

        boolean matches(String abi) {
            return mABIList.contains(abi);
        }
@@ -269,12 +311,13 @@ public class ZygoteProcess {
                                                  @Nullable String packageName,
                                                  @Nullable String[] packagesForUid,
                                                  @Nullable String[] visibleVols,
                                                  boolean useBlastulaPool,
                                                  @Nullable String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/false,
                    packageName, packagesForUid, visibleVols, zygoteArgs);
                    packageName, packagesForUid, visibleVols, useBlastulaPool, zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
@@ -322,14 +365,12 @@ public class ZygoteProcess {
     */
    @GuardedBy("mLock")
    private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, ArrayList<String> args)
            ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args)
            throws ZygoteStartFailedEx {
        try {
        // Throw early if any of the arguments are malformed. This means we can
        // avoid writing a partial response to the zygote.
            int sz = args.size();
            for (int i = 0; i < sz; i++) {
                if (args.get(i).indexOf('\n') >= 0) {
        for (String arg : args) {
            if (arg.indexOf('\n') >= 0) {
                throw new ZygoteStartFailedEx("embedded newlines not allowed");
            }
        }
@@ -344,37 +385,108 @@ public class ZygoteProcess {
         * the child or -1 on failure, followed by boolean to
         * indicate whether a wrapper process was used.
         */
            final BufferedWriter writer = zygoteState.mZygoteOutputWriter;
            final DataInputStream inputStream = zygoteState.mZygoteInputStream;
        String msgStr = Integer.toString(args.size()) + "\n"
                        + String.join("\n", args) + "\n";

            writer.write(Integer.toString(args.size()));
            writer.newLine();
        // Should there be a timeout on this?
        Process.ProcessStartResult result = new Process.ProcessStartResult();

            for (int i = 0; i < sz; i++) {
                String arg = args.get(i);
                writer.write(arg);
                writer.newLine();
        // TODO (chriswailes): Move branch body into separate function.
        if (useBlastulaPool && Zygote.BLASTULA_POOL_ENABLED && isValidBlastulaCommand(args)) {
            LocalSocket blastulaSessionSocket = null;

            try {
                blastulaSessionSocket = zygoteState.getBlastulaSessionSocket();

                final BufferedWriter blastulaWriter =
                        new BufferedWriter(
                                new OutputStreamWriter(blastulaSessionSocket.getOutputStream()),
                                Zygote.SOCKET_BUFFER_SIZE);
                final DataInputStream blastulaReader =
                        new DataInputStream(blastulaSessionSocket.getInputStream());

                blastulaWriter.write(msgStr);
                blastulaWriter.flush();

                result.pid = blastulaReader.readInt();
                // Blastulas can't be used to spawn processes that need wrappers.
                result.usingWrapper = false;

                if (result.pid < 0) {
                    throw new ZygoteStartFailedEx("Blastula specialization failed");
                }

            writer.flush();
                return result;
            } catch (IOException ex) {
                // 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());
            } finally {
                try {
                    blastulaSessionSocket.close();
                } catch (IOException ex) {
                    Log.e(LOG_TAG, "Failed to close blastula session socket: " + ex.getMessage());
                }
            }
        }

            // Should there be a timeout on this?
            Process.ProcessStartResult result = new Process.ProcessStartResult();
        try {
            final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
            final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;

            zygoteWriter.write(msgStr);
            zygoteWriter.flush();

            // Always read the entire result from the input stream to avoid leaving
            // bytes in the stream for future process starts to accidentally stumble
            // upon.
            result.pid = inputStream.readInt();
            result.usingWrapper = inputStream.readBoolean();
            result.pid = zygoteInputStream.readInt();
            result.usingWrapper = zygoteInputStream.readBoolean();
        } catch (IOException ex) {
            zygoteState.close();
            Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
                    + ex.toString());
            throw new ZygoteStartFailedEx(ex);
        }

        if (result.pid < 0) {
            throw new ZygoteStartFailedEx("fork() failed");
        }

        return result;
        } catch (IOException ex) {
            zygoteState.close();
            throw new ZygoteStartFailedEx(ex);
    }

    /**
     * Flags that may not be passed to a blastula.
     */
    private static final String[] INVALID_BLASTULA_FLAGS = {
        "--query-abi-list",
        "--get-pid",
        "--preload-default",
        "--preload-package",
        "--preload-app",
        "--start-child-zygote",
        "--set-api-blacklist-exemptions",
        "--hidden-api-log-sampling-rate",
        "--invoke-with"
    };

    /**
     * Tests a command list to see if it is valid to send to a blastula.
     * @param args  Zygote/Blastula command arguments
     * @return  True if the command can be passed to a blastula; false otherwise
     */
    private static boolean isValidBlastulaCommand(ArrayList<String> args) {
        for (String flag : args) {
            for (String badFlag : INVALID_BLASTULA_FLAGS) {
                if (flag.startsWith(badFlag)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
@@ -416,6 +528,7 @@ public class ZygoteProcess {
                                                      @Nullable String packageName,
                                                      @Nullable String[] packagesForUid,
                                                      @Nullable String[] visibleVols,
                                                      boolean useBlastulaPool,
                                                      @Nullable String[] extraArgs)
                                                      throws ZygoteStartFailedEx {
        ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -522,7 +635,9 @@ public class ZygoteProcess {
        }

        synchronized(mLock) {
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                              useBlastulaPool,
                                              argsForZygote);
        }
    }

@@ -686,7 +801,7 @@ public class ZygoteProcess {
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                primaryZygoteState =
                    ZygoteState.connect(mZygoteSocketAddress);
                    ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }
@@ -703,7 +818,8 @@ public class ZygoteProcess {
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
                secondaryZygoteState =
                    ZygoteState.connect(mZygoteSecondarySocketAddress);
                    ZygoteState.connect(mZygoteSecondarySocketAddress,
                                        mBlastulaPoolSecondarySocketAddress);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }
@@ -820,7 +936,7 @@ public class ZygoteProcess {
        for (int n = 20; n >= 0; n--) {
            try {
                final ZygoteState zs =
                        ZygoteState.connect(zygoteSocketAddress);
                        ZygoteState.connect(zygoteSocketAddress, null);
                zs.close();
                return;
            } catch (IOException ioe) {
@@ -884,7 +1000,8 @@ public class ZygoteProcess {
                    gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
                    abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
                    true /* startChildZygote */, null /* packageName */,
                    null /* packagesForUid */, null /* visibleVolumes */, extraArgs);
                    null /* packagesForUid */, null /* visibleVolumes */,
                    false /* useBlastulaPool */, extraArgs);
        } catch (ZygoteStartFailedEx ex) {
            throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
        }
+19 −8
Original line number Diff line number Diff line
@@ -117,7 +117,13 @@ public final class Zygote {
    /** Number of bytes sent to the Zygote over blastula pipes or the pool event FD */
    public static final int BLASTULA_MANAGEMENT_MESSAGE_BYTES = 8;

    /** If the blastula pool should be created and used to start applications */
    /**
     * If the blastula pool should be created and used to start applications.
     *
     * Setting this value to false will disable the creation, maintenance, and use of the blastula
     * pool.  When the blastula pool is disabled the application lifecycle will be identical to
     * previous versions of Android.
     */
    public static final boolean BLASTULA_POOL_ENABLED = false;

    /**
@@ -187,6 +193,11 @@ public final class Zygote {
    // TODO (chriswailes): This must be updated at the same time as sBlastulaPoolMax.
    static int sBlastulaPoolRefillThreshold = (sBlastulaPoolMax / 2);

    /**
     * @hide for internal use only
     */
    public static final int SOCKET_BUFFER_SIZE = 256;

    private static LocalServerSocket sBlastulaPoolSocket = null;

    /** a prototype instance for a future List.toArray() */
@@ -234,14 +245,14 @@ public final class Zygote {
    public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
            int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
            String packageName, String[] packagesForUid, String[] visibleVolIds) {
            String packageName, String[] packagesForUID, String[] visibleVolIDs) {
        VM_HOOKS.preFork();
        // Resets nice priority for zygote process.
        resetNicePriority();
        int pid = nativeForkAndSpecialize(
                uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName,
                packagesForUid, visibleVolIds);
                packagesForUID, visibleVolIDs);
        // Enable tracing as soon as possible for the child process.
        if (pid == 0) {
            Trace.setTracingEnabled(true, runtimeFlags);
@@ -256,7 +267,7 @@ public final class Zygote {
    private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
            int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
            int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
            String appDataDir, String packageName, String[] packagesForUid, String[] visibleVolIds);
            String appDataDir, String packageName, String[] packagesForUID, String[] visibleVolIDs);

    /**
     * Specialize a Blastula instance.  The current VM must have been started
@@ -281,12 +292,12 @@ public final class Zygote {
     */
    public static void specializeBlastula(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, int mountExternal, String seInfo, String niceName,
            boolean startChildZygote, String instructionSet, String appDataDir,
            String packageName, String[] packagesForUid, String[] visibleVolIds) {
            boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
            String[] packagesForUID, String[] visibleVolIDs) {

        nativeSpecializeBlastula(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                                 niceName, startChildZygote, instructionSet, appDataDir,
                                 packageName, packagesForUid, visibleVolIds);
                                 packageName, packagesForUID, visibleVolIDs);

        // Enable tracing as soon as possible for the child process.
        Trace.setTracingEnabled(true, runtimeFlags);
@@ -306,7 +317,7 @@ public final class Zygote {
    private static native void nativeSpecializeBlastula(int uid, int gid, int[] gids,
            int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
            boolean startChildZygote, String instructionSet, String appDataDir, String packageName,
            String[] packagesForUid, String[] visibleVolIds);
            String[] packagesForUID, String[] visibleVolIDs);

    /**
     * Called to do any initialization before starting an application.
+1 −1
Original line number Diff line number Diff line
@@ -242,7 +242,7 @@ class ZygoteConnection {
            fdsToClose[0] = fd.getInt$();
        }

        fd = zygoteServer.getServerSocketFileDescriptor();
        fd = zygoteServer.getZygoteSocketFileDescriptor();

        if (fd != null) {
            fdsToClose[1] = fd.getInt$();
+24 −7
Original line number Diff line number Diff line
@@ -269,7 +269,7 @@ public class ZygoteInit {

        try {
            BufferedReader br =
                    new BufferedReader(new InputStreamReader(is), 256);
                    new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);

            int count = 0;
            String line;
@@ -750,7 +750,7 @@ public class ZygoteInit {
            throw new RuntimeException("Failed to setpgid(0,0)", ex);
        }

        final Runnable caller;
        Runnable caller;
        try {
            // Report Zygote start time to tron unless it is a runtime restart
            if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -786,7 +786,17 @@ public class ZygoteInit {
                throw new RuntimeException("No ABI list supplied.");
            }

            zygoteServer.registerServerSocketFromEnv(socketName);
            // TODO (chriswailes): Wrap these three calls in a helper function?
            final String blastulaSocketName =
                    socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME)
                            ? ZygoteProcess.BLASTULA_POOL_SOCKET_NAME
                            : ZygoteProcess.BLASTULA_POOL_SECONDARY_SOCKET_NAME;

            zygoteServer.createZygoteSocket(socketName);
            Zygote.createBlastulaSocket(blastulaSocketName);

            Zygote.getSocketFDs(socketName.equals(ZygoteProcess.ZYGOTE_SOCKET_NAME));

            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
@@ -829,11 +839,18 @@ public class ZygoteInit {
                }
            }

            // If the return value is null then this is the zygote process
            // returning to the normal control flow.  If it returns a Runnable
            // object then this is a blastula that has finished specializing.
            caller = Zygote.initBlastulaPool();

            if (caller == null) {
                Log.i(TAG, "Accepting command socket connections");

                // The select loop returns early in the child process after a fork and
                // loops forever in the zygote.
                caller = zygoteServer.runSelectLoop(abiList);
            }
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            throw ex;
Loading