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
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index c7afd41..64d14c0 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -498,7 +498,8 @@
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 @@
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 f0bdaec..3d28a5e 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -72,6 +72,16 @@
/**
* @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 @@
* 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 @@
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 static class ZygoteState {
final LocalSocketAddress mZygoteSocketAddress;
+ final LocalSocketAddress mBlastulaSocketAddress;
private final LocalSocket mZygoteSessionSocket;
@@ -118,11 +148,13 @@
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 @@
}
/**
- * 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 @@
zygoteOutputWriter =
new BufferedWriter(
new OutputStreamWriter(zygoteSessionSocket.getOutputStream()),
- 256);
+ Zygote.SOCKET_BUFFER_SIZE);
} catch (IOException ex) {
try {
zygoteSessionSocket.close();
@@ -159,11 +194,18 @@
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 @@
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 @@
*/
@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.
+ */
+ String msgStr = Integer.toString(args.size()) + "\n"
+ + String.join("\n", args) + "\n";
+
+ // 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());
}
}
+ }
- /**
- * 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;
+ try {
+ final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
+ final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;
- writer.write(Integer.toString(args.size()));
- writer.newLine();
-
- for (int i = 0; i < sz; i++) {
- String arg = args.get(i);
- writer.write(arg);
- writer.newLine();
- }
-
- writer.flush();
-
- // 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 @@
String appDataDir,
String invokeWith,
boolean startChildZygote,
+ boolean useBlastulaPool,
String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -469,7 +582,9 @@
}
synchronized(mLock) {
- return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
+ return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
+ useBlastulaPool,
+ argsForZygote);
}
}
@@ -633,7 +748,7 @@
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 @@
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 @@
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 @@
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 09ea028..3859b95 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -105,7 +105,13 @@
/** 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 @@
// 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 43f114f..ab356a6 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -224,7 +224,7 @@
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 2f00c07..e3e55ed 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -269,7 +269,7 @@
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 @@
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 @@
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 @@
}
}
- 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 c1bfde1..680d649 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.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 @@
* 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 @@
}
/**
- * 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 @@
* 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 @@
*/
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 @@
*/
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 @@
Log.e(TAG, "Zygote: error closing descriptor", ex);
}
- mServerSocket = null;
+ mZygoteSocket = null;
}
/**
@@ -160,8 +147,8 @@
* closure after a child process is forked off.
*/
- FileDescriptor getServerSocketFileDescriptor() {
- return mServerSocket.getFileDescriptor();
+ FileDescriptor getZygoteSocketFileDescriptor() {
+ return mZygoteSocket.getFileDescriptor();
}
/**
@@ -170,36 +157,67 @@
* 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 @@
}
// 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 @@
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 @@
// 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 569a894..4b994c3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1252,7 +1252,7 @@
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 @@
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);