summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Wailes <chriswailes@google.com> 2019-01-11 17:13:00 -0800
committer Chris Wailes <chriswailes@google.com> 2019-01-24 16:50:05 -0800
commitcffbf1c9b4d6ea7fa58dba77073092f8f4571a00 (patch)
treebb8a5b328ece7a41159acf52dc88eedd3242a82e
parentc37ebe1313e207ff07bc6e31c6c75447487176c7 (diff)
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 Change-Id: I46c32ad09400591e866b6c6121d5a9b0332092f3 Merged-In: I46c32ad09400591e866b6c6121d5a9b0332092f3
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/android/os/ZygoteProcess.java212
-rw-r--r--core/java/com/android/internal/os/Zygote.java13
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java2
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java31
-rw-r--r--core/java/com/android/internal/os/ZygoteServer.java185
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp4
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);