diff options
| -rw-r--r-- | core/java/android/os/Process.java | 6 | ||||
| -rw-r--r-- | core/java/android/os/ZygoteProcess.java | 212 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/Zygote.java | 13 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteConnection.java | 2 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteInit.java | 31 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteServer.java | 185 | ||||
| -rw-r--r-- | core/jni/com_android_internal_os_Zygote.cpp | 4 |
7 files changed, 333 insertions, 120 deletions
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index c7afd411936c..64d14c0b51ca 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -498,7 +498,8 @@ public class Process { String[] zygoteArgs) { return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, + /*useBlastulaPool=*/ true, zygoteArgs); } /** @hide */ @@ -515,7 +516,8 @@ public class Process { String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, + /*useBlastulaPool=*/ false, zygoteArgs); } /** diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index f0bdaecca1ef..3d28a5ec02b0 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -72,6 +72,16 @@ public class ZygoteProcess { /** * @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 + */ private static final String LOG_TAG = "ZygoteProcess"; /** @@ -83,6 +93,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 = @@ -90,12 +109,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() { @@ -107,6 +136,7 @@ public class ZygoteProcess { */ public static class ZygoteState { final LocalSocketAddress mZygoteSocketAddress; + final LocalSocketAddress mBlastulaSocketAddress; private final LocalSocket mZygoteSessionSocket; @@ -118,11 +148,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; @@ -130,14 +162,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; @@ -150,7 +185,7 @@ public class ZygoteProcess { zygoteOutputWriter = new BufferedWriter( new OutputStreamWriter(zygoteSessionSocket.getOutputStream()), - 256); + Zygote.SOCKET_BUFFER_SIZE); } catch (IOException ex) { try { zygoteSessionSocket.close(); @@ -159,11 +194,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); } @@ -259,12 +301,14 @@ public class ZygoteProcess { String instructionSet, String appDataDir, String invokeWith, + boolean useBlastulaPool, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, - /*startChildZygote=*/false, zygoteArgs); + /*startChildZygote=*/false, + useBlastulaPool, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -312,59 +356,127 @@ 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) { - throw new ZygoteStartFailedEx("embedded newlines not allowed"); - } + // Throw early if any of the arguments are malformed. This means we can + // avoid writing a partial response to the zygote. + for (String arg : args) { + if (arg.indexOf('\n') >= 0) { + throw new ZygoteStartFailedEx("embedded newlines not allowed"); } + } - /** - * See com.android.internal.os.SystemZygoteInit.readArgumentList() - * Presently the wire format to the zygote process is: - * a) a count of arguments (argc, in essence) - * b) a number of newline-separated argument strings equal to count - * - * After the zygote process reads these it will write the pid of - * 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; - - writer.write(Integer.toString(args.size())); - writer.newLine(); + /** + * See com.android.internal.os.SystemZygoteInit.readArgumentList() + * Presently the wire format to the zygote process is: + * a) a count of arguments (argc, in essence) + * b) a number of newline-separated argument strings equal to count + * + * After the zygote process reads these it will write the pid of + * the child or -1 on failure, followed by boolean to + * indicate whether a wrapper process was used. + */ + String msgStr = Integer.toString(args.size()) + "\n" + + String.join("\n", args) + "\n"; - for (int i = 0; i < sz; i++) { - String arg = args.get(i); - writer.write(arg); - writer.newLine(); + // Should there be a timeout on this? + Process.ProcessStartResult result = new Process.ProcessStartResult(); + + // 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"); + } + + 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()); + } } + } - writer.flush(); + try { + final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter; + final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream; - // Should there be a timeout on this? - Process.ProcessStartResult result = new Process.ProcessStartResult(); + 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(); - - if (result.pid < 0) { - throw new ZygoteStartFailedEx("fork() failed"); - } - return result; + 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; + } + + /** + * 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", + "--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; } /** @@ -400,6 +512,7 @@ public class ZygoteProcess { String appDataDir, String invokeWith, boolean startChildZygote, + boolean useBlastulaPool, String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -469,7 +582,9 @@ public class ZygoteProcess { } synchronized(mLock) { - return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote); + return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), + useBlastulaPool, + argsForZygote); } } @@ -633,7 +748,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); } @@ -650,7 +765,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); } @@ -737,7 +853,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) { @@ -778,7 +894,7 @@ public class ZygoteProcess { result = startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, - true /* startChildZygote */, extraArgs); + true /* startChildZygote */, false /* useBlastulaPool */, extraArgs); } catch (ZygoteStartFailedEx ex) { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 09ea02826916..3859b951ed45 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -105,7 +105,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; /** @@ -155,6 +161,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() */ diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 43f114f010bc..ab356a6f9a99 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -224,7 +224,7 @@ class ZygoteConnection { fdsToClose[0] = fd.getInt$(); } - fd = zygoteServer.getServerSocketFileDescriptor(); + fd = zygoteServer.getZygoteSocketFileDescriptor(); if (fd != null) { fdsToClose[1] = fd.getInt$(); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 2f00c07247db..e3e55ed28c6f 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -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 { } } - Log.i(TAG, "Accepting command socket connections"); + // 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(); - // The select loop returns early in the child process after a fork and - // loops forever in the zygote. - caller = zygoteServer.runSelectLoop(abiList); + 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; diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index c1bfde194c4e..680d64935b2d 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -26,6 +26,8 @@ import android.system.StructPollfd; import android.util.Log; import android.util.Slog; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.util.ArrayList; @@ -40,18 +42,17 @@ import java.util.ArrayList; * client protocol. */ class ZygoteServer { + // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate public static final String TAG = "ZygoteServer"; - private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; - /** * Listening socket that accepts new server connections. */ - private LocalServerSocket mServerSocket; + private LocalServerSocket mZygoteSocket; /** - * Whether or not mServerSocket's underlying FD should be closed directly. - * If mServerSocket is created with an existing FD, closing the socket does + * Whether or not mZygoteSocket's underlying FD should be closed directly. + * If mZygoteSocket is created with an existing FD, closing the socket does * not close the FD and it must be closed explicitly. If the socket is created * with a name instead, then closing the socket will close the underlying FD * and it should not be double-closed. @@ -70,31 +71,17 @@ class ZygoteServer { } /** - * Registers a server socket for zygote command connections. This locates the server socket - * file descriptor through an ANDROID_SOCKET_ environment variable. + * Creates a managed object representing the Zygote socket that has already + * been initialized and bound by init. + * + * TODO (chriswailes): Move the name selection logic into this function. * * @throws RuntimeException when open fails */ - void registerServerSocketFromEnv(String socketName) { - if (mServerSocket == null) { - int fileDesc; - final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; - try { - String env = System.getenv(fullSocketName); - fileDesc = Integer.parseInt(env); - } catch (RuntimeException ex) { - throw new RuntimeException(fullSocketName + " unset or invalid", ex); - } - - try { - FileDescriptor fd = new FileDescriptor(); - fd.setInt$(fileDesc); - mServerSocket = new LocalServerSocket(fd); - mCloseSocketFd = true; - } catch (IOException ex) { - throw new RuntimeException( - "Error binding to local socket '" + fileDesc + "'", ex); - } + void createZygoteSocket(String socketName) { + if (mZygoteSocket == null) { + mZygoteSocket = Zygote.createManagedSocketFromInitSocket(socketName); + mCloseSocketFd = true; } } @@ -103,9 +90,9 @@ class ZygoteServer { * at the specified name in the abstract socket namespace. */ void registerServerSocketAtAbstractName(String socketName) { - if (mServerSocket == null) { + if (mZygoteSocket == null) { try { - mServerSocket = new LocalServerSocket(socketName); + mZygoteSocket = new LocalServerSocket(socketName); mCloseSocketFd = false; } catch (IOException ex) { throw new RuntimeException( @@ -120,7 +107,7 @@ class ZygoteServer { */ private ZygoteConnection acceptCommandPeer(String abiList) { try { - return createNewConnection(mServerSocket.accept(), abiList); + return createNewConnection(mZygoteSocket.accept(), abiList); } catch (IOException ex) { throw new RuntimeException( "IOException during accept()", ex); @@ -138,9 +125,9 @@ class ZygoteServer { */ void closeServerSocket() { try { - if (mServerSocket != null) { - FileDescriptor fd = mServerSocket.getFileDescriptor(); - mServerSocket.close(); + if (mZygoteSocket != null) { + FileDescriptor fd = mZygoteSocket.getFileDescriptor(); + mZygoteSocket.close(); if (fd != null && mCloseSocketFd) { Os.close(fd); } @@ -151,7 +138,7 @@ class ZygoteServer { Log.e(TAG, "Zygote: error closing descriptor", ex); } - mServerSocket = null; + mZygoteSocket = null; } /** @@ -160,8 +147,8 @@ class ZygoteServer { * closure after a child process is forked off. */ - FileDescriptor getServerSocketFileDescriptor() { - return mServerSocket.getFileDescriptor(); + FileDescriptor getZygoteSocketFileDescriptor() { + return mZygoteSocket.getFileDescriptor(); } /** @@ -170,36 +157,67 @@ class ZygoteServer { * worth at a time. */ Runnable runSelectLoop(String abiList) { - ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>(); + ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>(); ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>(); - fds.add(mServerSocket.getFileDescriptor()); + socketFDs.add(mZygoteSocket.getFileDescriptor()); peers.add(null); while (true) { - StructPollfd[] pollFds = new StructPollfd[fds.size()]; - for (int i = 0; i < pollFds.length; ++i) { - pollFds[i] = new StructPollfd(); - pollFds[i].fd = fds.get(i); - pollFds[i].events = (short) POLLIN; + int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs(); + + // 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]; + + int pollIndex = 0; + for (FileDescriptor socketFD : socketFDs) { + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = socketFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + } + + final int blastulaPoolEventFDIndex = pollIndex; + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = Zygote.sBlastulaPoolEventFD; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; + + for (int blastulaPipeFD : blastulaPipeFDs) { + FileDescriptor managedFd = new FileDescriptor(); + managedFd.setInt$(blastulaPipeFD); + + pollFDs[pollIndex] = new StructPollfd(); + pollFDs[pollIndex].fd = managedFd; + pollFDs[pollIndex].events = (short) POLLIN; + ++pollIndex; } + try { - Os.poll(pollFds, -1); + Os.poll(pollFDs, -1); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } - for (int i = pollFds.length - 1; i >= 0; --i) { - if ((pollFds[i].revents & POLLIN) == 0) { + + while (--pollIndex >= 0) { + if ((pollFDs[pollIndex].revents & POLLIN) == 0) { continue; } - if (i == 0) { + if (pollIndex == 0) { + // Zygote server socket + ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); - fds.add(newPeer.getFileDescriptor()); - } else { + socketFDs.add(newPeer.getFileDescriptor()); + + } else if (pollIndex < blastulaPoolEventFDIndex) { + // Session socket accepted from the Zygote server socket + try { - ZygoteConnection connection = peers.get(i); + ZygoteConnection connection = peers.get(pollIndex); final Runnable command = connection.processOneCommand(this); if (mIsForkChild) { @@ -217,12 +235,12 @@ class ZygoteServer { } // We don't know whether the remote side of the socket was closed or - // not until we attempt to read from it from processOneCommand. This shows up as - // a regular POLLIN event in our regular processing loop. + // not until we attempt to read from it from processOneCommand. This + // shows up as a regular POLLIN event in our regular processing loop. if (connection.isClosedByPeer()) { connection.closeSocket(); - peers.remove(i); - fds.remove(i); + peers.remove(pollIndex); + socketFDs.remove(pollIndex); } } } catch (Exception e) { @@ -234,13 +252,13 @@ class ZygoteServer { Slog.e(TAG, "Exception executing zygote command: ", e); - // Make sure the socket is closed so that the other end knows immediately - // that something has gone wrong and doesn't time out waiting for a - // response. - ZygoteConnection conn = peers.remove(i); + // Make sure the socket is closed so that the other end knows + // immediately that something has gone wrong and doesn't time out + // waiting for a response. + ZygoteConnection conn = peers.remove(pollIndex); conn.closeSocket(); - fds.remove(i); + socketFDs.remove(pollIndex); } else { // We're in the child so any exception caught here has happened post // fork and before we execute ActivityThread.main (or any other main() @@ -254,6 +272,55 @@ class ZygoteServer { // is returned. mIsForkChild = false; } + } else { + // Either the blastula pool event FD or a blastula reporting pipe. + + // If this is the event FD the payload will be the number of blastulas removed. + // If this is a reporting pipe FD the payload will be the PID of the blastula + // that was just specialized. + long messagePayload = -1; + + try { + byte[] buffer = new byte[Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES]; + int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length); + + if (readBytes == Zygote.BLASTULA_MANAGEMENT_MESSAGE_BYTES) { + DataInputStream inputStream = + new DataInputStream(new ByteArrayInputStream(buffer)); + + messagePayload = inputStream.readLong(); + } else { + Log.e(TAG, "Incomplete read from blastula management FD of size " + + readBytes); + continue; + } + } catch (Exception ex) { + if (pollIndex == blastulaPoolEventFDIndex) { + Log.e(TAG, "Failed to read from blastula pool event FD: " + + ex.getMessage()); + } else { + Log.e(TAG, "Failed to read from blastula reporting pipe: " + + ex.getMessage()); + } + + continue; + } + + if (pollIndex > blastulaPoolEventFDIndex) { + Zygote.removeBlastulaTableEntry((int) messagePayload); + } + + int[] sessionSocketRawFDs = + socketFDs.subList(1, socketFDs.size()) + .stream() + .mapToInt(fd -> fd.getInt$()) + .toArray(); + + final Runnable command = Zygote.fillBlastulaPool(sessionSocketRawFDs); + + if (command != null) { + return command; + } } } } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 569a89443dca..4b994c36c547 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1252,7 +1252,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( fds_to_close.insert(fds_to_close.end(), blastula_pipes.begin(), blastula_pipes.end()); fds_to_ignore.insert(fds_to_ignore.end(), blastula_pipes.begin(), blastula_pipes.end()); -// fds_to_close.push_back(gBlastulaPoolSocketFD); + fds_to_close.push_back(gBlastulaPoolSocketFD); if (gBlastulaPoolEventFD != -1) { fds_to_close.push_back(gBlastulaPoolEventFD); @@ -1277,7 +1277,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( std::vector<int> fds_to_close(MakeBlastulaPipeReadFDVector()), fds_to_ignore(fds_to_close); -// fds_to_close.push_back(gBlastulaPoolSocketFD); + fds_to_close.push_back(gBlastulaPoolSocketFD); if (gBlastulaPoolEventFD != -1) { fds_to_close.push_back(gBlastulaPoolEventFD); |