diff options
| -rw-r--r-- | core/java/android/os/ZygoteProcess.java | 140 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/Zygote.java | 31 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteArguments.java | 10 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteConnection.java | 92 | ||||
| -rw-r--r-- | core/java/com/android/internal/os/ZygoteServer.java | 214 | ||||
| -rw-r--r-- | core/jni/com_android_internal_os_Zygote.cpp | 57 |
6 files changed, 399 insertions, 145 deletions
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 370381c9cb10..e49b65e63c77 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -27,7 +27,6 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.Zygote; -import com.android.internal.util.Preconditions; import java.io.BufferedWriter; import java.io.DataInputStream; @@ -122,8 +121,9 @@ public class ZygoteProcess { new LocalSocketAddress(Zygote.BLASTULA_POOL_SECONDARY_SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); - // TODO (chriswailes): Uncomment when the blastula pool can be enabled. -// fetchBlastulaPoolEnabledProp(); + if (fetchBlastulaPoolEnabledProp()) { + informZygotesOfBlastulaPoolStatus(); + } } public ZygoteProcess(LocalSocketAddress primarySocketAddress, @@ -325,12 +325,9 @@ public class ZygoteProcess { @Nullable String sandboxId, boolean useBlastulaPool, @Nullable String[] zygoteArgs) { - if (fetchBlastulaPoolEnabledProp()) { - // TODO (chriswailes): Send the appropriate command to the zygotes - Log.i(LOG_TAG, "Blastula pool enabled property set to: " + mBlastulaPoolEnabled); - - // This can't be enabled yet, but we do want to test this code path. - mBlastulaPoolEnabled = false; + // TODO (chriswailes): Is there a better place to check this value? + if (fetchBlastulaPoolEnabledPropWithMinInterval()) { + informZygotesOfBlastulaPoolStatus(); } try { @@ -385,7 +382,7 @@ public class ZygoteProcess { * @throws ZygoteStartFailedEx if process start failed for any reason */ @GuardedBy("mLock") - private static Process.ProcessStartResult zygoteSendArgsAndGetResult( + private Process.ProcessStartResult zygoteSendArgsAndGetResult( ZygoteState zygoteState, boolean useBlastulaPool, ArrayList<String> args) throws ZygoteStartFailedEx { // Throw early if any of the arguments are malformed. This means we can @@ -413,7 +410,7 @@ public class ZygoteProcess { Process.ProcessStartResult result = new Process.ProcessStartResult(); // TODO (chriswailes): Move branch body into separate function. - if (useBlastulaPool && isValidBlastulaCommand(args)) { + if (useBlastulaPool && mBlastulaPoolEnabled && isValidBlastulaCommand(args)) { LocalSocket blastulaSessionSocket = null; try { @@ -442,7 +439,7 @@ public class ZygoteProcess { // 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()); + + ex.getMessage()); } finally { try { blastulaSessionSocket.close(); @@ -648,7 +645,7 @@ public class ZygoteProcess { synchronized(mLock) { return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), - useBlastulaPool && mBlastulaPoolEnabled, + useBlastulaPool, argsForZygote); } } @@ -668,6 +665,10 @@ public class ZygoteProcess { Boolean.parseBoolean(BLASTULA_POOL_ENABLED_DEFAULT)); } + if (origVal != mBlastulaPoolEnabled) { + Log.i(LOG_TAG, "blastulaPoolEnabled = " + mBlastulaPoolEnabled); + } + return origVal != mBlastulaPoolEnabled; } @@ -830,49 +831,57 @@ public class ZygoteProcess { } /** - * Tries to open a session socket to a Zygote process with a compatible ABI if one is not - * already open. If a compatible session socket is already open that session socket is returned. - * This function may block and may have to try connecting to multiple Zygotes to find the - * appropriate one. Requires that mLock be held. + * Creates a ZygoteState for the primary zygote if it doesn't exist or has been disconnected. */ @GuardedBy("mLock") - private ZygoteState openZygoteSocketIfNeeded(String abi) - throws ZygoteStartFailedEx { - - Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held"); - + private void attemptConnectionToPrimaryZygote() throws IOException { if (primaryZygoteState == null || primaryZygoteState.isClosed()) { - try { - primaryZygoteState = + primaryZygoteState = ZygoteState.connect(mZygoteSocketAddress, mBlastulaPoolSocketAddress); - } catch (IOException ioe) { - throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe); - } maybeSetApiBlacklistExemptions(primaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState); } + } - if (primaryZygoteState.matches(abi)) { - return primaryZygoteState; - } - - // The primary zygote didn't match. Try the secondary. + /** + * Creates a ZygoteState for the secondary zygote if it doesn't exist or has been disconnected. + */ + @GuardedBy("mLock") + private void attemptConnectionToSecondaryZygote() throws IOException { if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) { - try { - secondaryZygoteState = + secondaryZygoteState = ZygoteState.connect(mZygoteSecondarySocketAddress, - mBlastulaPoolSecondarySocketAddress); - } catch (IOException ioe) { - throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe); - } + mBlastulaPoolSecondarySocketAddress); maybeSetApiBlacklistExemptions(secondaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState); } + } - if (secondaryZygoteState.matches(abi)) { - return secondaryZygoteState; + /** + * Tries to open a session socket to a Zygote process with a compatible ABI if one is not + * already open. If a compatible session socket is already open that session socket is returned. + * This function may block and may have to try connecting to multiple Zygotes to find the + * appropriate one. Requires that mLock be held. + */ + @GuardedBy("mLock") + private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { + try { + attemptConnectionToPrimaryZygote(); + + if (primaryZygoteState.matches(abi)) { + return primaryZygoteState; + } + + // The primary zygote didn't match. Try the secondary. + attemptConnectionToSecondaryZygote(); + + if (secondaryZygoteState.matches(abi)) { + return secondaryZygoteState; + } + } catch (IOException ioe) { + throw new ZygoteStartFailedEx("Error connecting to zygote", ioe); } throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi); @@ -998,6 +1007,57 @@ public class ZygoteProcess { } /** + * Sends messages to the zygotes telling them to change the status of their blastula pools. If + * this notification fails the ZygoteProcess will fall back to the previous behavior. + */ + private void informZygotesOfBlastulaPoolStatus() { + final String command = "1\n--blastula-pool-enabled=" + mBlastulaPoolEnabled + "\n"; + + synchronized (mLock) { + try { + attemptConnectionToPrimaryZygote(); + + primaryZygoteState.mZygoteOutputWriter.write(command); + primaryZygoteState.mZygoteOutputWriter.flush(); + } catch (IOException ioe) { + mBlastulaPoolEnabled = !mBlastulaPoolEnabled; + Log.w(LOG_TAG, "Failed to inform zygotes of blastula pool status: " + + ioe.getMessage()); + return; + } + + try { + attemptConnectionToSecondaryZygote(); + + try { + secondaryZygoteState.mZygoteOutputWriter.write(command); + secondaryZygoteState.mZygoteOutputWriter.flush(); + + // Wait for the secondary Zygote to finish its work. + secondaryZygoteState.mZygoteInputStream.readInt(); + } catch (IOException ioe) { + throw new IllegalStateException( + "Blastula pool state change cause an irrecoverable error", + ioe); + } + } catch (IOException ioe) { + // No secondary zygote present. This is expected on some devices. + } + + // Wait for the response from the primary zygote here so the primary/secondary zygotes + // can work concurrently. + try { + // Wait for the primary zygote to finish its work. + primaryZygoteState.mZygoteInputStream.readInt(); + } catch (IOException ioe) { + throw new IllegalStateException( + "Blastula pool state change cause an irrecoverable error", + ioe); + } + } + } + + /** * Starts a new zygote process as a child of this zygote. This is used to create * secondary zygotes that inherit data from the zygote that this object * communicates with. This returns a new ZygoteProcess representing a connection diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index b2fecaf52a5c..58b48d88fa38 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -162,9 +162,9 @@ public final class Zygote { /** * The duration to wait before re-checking Zygote related system properties. * - * Five minutes in milliseconds. + * One minute in milliseconds. */ - public static final long PROPERTY_CHECK_INTERVAL = 300000; + public static final long PROPERTY_CHECK_INTERVAL = 60000; /** * @hide for internal use only @@ -427,6 +427,12 @@ public final class Zygote { defaultValue); } + protected static void emptyBlastulaPool() { + nativeEmptyBlastulaPool(); + } + + private static native void nativeEmptyBlastulaPool(); + /** * Returns the value of a system property converted to a boolean using specific logic. * @@ -520,7 +526,7 @@ public final class Zygote { LocalSocket sessionSocket = null; DataOutputStream blastulaOutputStream = null; Credentials peerCredentials = null; - String[] argStrings = null; + ZygoteArguments args = null; while (true) { try { @@ -533,25 +539,24 @@ public final class Zygote { peerCredentials = sessionSocket.getPeerCredentials(); - argStrings = readArgumentList(blastulaReader); + String[] argStrings = readArgumentList(blastulaReader); if (argStrings != null) { + args = new ZygoteArguments(argStrings); + + // TODO (chriswailes): Should this only be run for debug builds? + validateBlastulaCommand(args); break; } else { Log.e("Blastula", "Truncated command received."); IoUtils.closeQuietly(sessionSocket); } - } catch (IOException ioEx) { - Log.e("Blastula", "Failed to read command: " + ioEx.getMessage()); + } catch (Exception ex) { + Log.e("Blastula", ex.getMessage()); IoUtils.closeQuietly(sessionSocket); } } - ZygoteArguments args = new ZygoteArguments(argStrings); - - // TODO (chriswailes): Should this only be run for debug builds? - validateBlastulaCommand(args); - applyUidSecurityPolicy(args, peerCredentials); applyDebuggerSystemProperty(args); @@ -740,8 +745,8 @@ public final class Zygote { if (args.mInvokeWith != null && peerUid != 0 && (args.mRuntimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { - throw new ZygoteSecurityException("Peer is permitted to specify an" - + "explicit invoke-with wrapper command only for debuggable" + throw new ZygoteSecurityException("Peer is permitted to specify an " + + "explicit invoke-with wrapper command only for debuggable " + "applications."); } } diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index 55b2e87bb24f..9cb5820a32ce 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -101,6 +101,12 @@ class ZygoteArguments { String mSeInfo; /** + * + */ + boolean mBlastulaPoolEnabled; + boolean mBlastulaPoolStatusSpecified = false; + + /** * from all --rlimit=r,c,m */ ArrayList<int[]> mRLimits; @@ -397,6 +403,10 @@ class ZygoteArguments { throw new IllegalArgumentException("Duplicate arg specified"); } mSandboxId = arg.substring(arg.indexOf('=') + 1); + } else if (arg.startsWith("--blastula-pool-enabled=")) { + mBlastulaPoolStatusSpecified = true; + mBlastulaPoolEnabled = Boolean.parseBoolean(arg.substring(arg.indexOf('=') + 1)); + expectRuntimeArgs = false; } else { break; } diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 7e1fddc54295..c7ba22df5700 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -158,6 +158,10 @@ class ZygoteConnection { return null; } + if (parsedArgs.mBlastulaPoolStatusSpecified) { + return handleBlastulaPoolStatusChange(zygoteServer, parsedArgs.mBlastulaPoolEnabled); + } + if (parsedArgs.mPreloadDefault) { handlePreload(); return null; @@ -185,13 +189,12 @@ class ZygoteConnection { } if (parsedArgs.mApiBlacklistExemptions != null) { - handleApiBlacklistExemptions(parsedArgs.mApiBlacklistExemptions); - return null; + return handleApiBlacklistExemptions(zygoteServer, parsedArgs.mApiBlacklistExemptions); } if (parsedArgs.mHiddenApiAccessLogSampleRate != -1) { - handleHiddenApiAccessLogSampleRate(parsedArgs.mHiddenApiAccessLogSampleRate); - return null; + return handleHiddenApiAccessLogSampleRate(zygoteServer, + parsedArgs.mHiddenApiAccessLogSampleRate); } if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) { @@ -325,10 +328,64 @@ class ZygoteConnection { } } - private void handleApiBlacklistExemptions(String[] exemptions) { + private Runnable stateChangeWithBlastulaPoolReset(ZygoteServer zygoteServer, + Runnable stateChangeCode) { try { - ZygoteInit.setApiBlacklistExemptions(exemptions); + if (zygoteServer.isBlastulaPoolEnabled()) { + Zygote.emptyBlastulaPool(); + } + + stateChangeCode.run(); + + if (zygoteServer.isBlastulaPoolEnabled()) { + Runnable fpResult = + zygoteServer.fillBlastulaPool( + new int[]{mSocket.getFileDescriptor().getInt$()}); + + if (fpResult != null) { + zygoteServer.setForkChild(); + return fpResult; + } + } + mSocketOutStream.writeInt(0); + + return null; + } catch (IOException ioe) { + throw new IllegalStateException("Error writing to command socket", ioe); + } + } + + /** + * Makes the necessary changes to implement a new API blacklist exemption policy, and then + * responds to the system server, letting it know that the task has been completed. + * + * This necessitates a change to the internal state of the Zygote. As such, if the blastula + * pool is enabled all existing blastulas have an incorrect API blacklist exemption list. To + * properly handle this request the pool must be emptied and refilled. This process can return + * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked. + * + * @param zygoteServer The server object that received the request + * @param exemptions The new exemption list. + * @return A Runnable object representing a new app in any blastulas spawned from here; the + * zygote process will always receive a null value from this function. + */ + private Runnable handleApiBlacklistExemptions(ZygoteServer zygoteServer, String[] exemptions) { + return stateChangeWithBlastulaPoolReset(zygoteServer, + () -> ZygoteInit.setApiBlacklistExemptions(exemptions)); + } + + private Runnable handleBlastulaPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) { + try { + Runnable fpResult = zygoteServer.setBlastulaPoolStatus(newStatus, mSocket); + + if (fpResult == null) { + mSocketOutStream.writeInt(0); + } else { + zygoteServer.setForkChild(); + } + + return fpResult; } catch (IOException ioe) { throw new IllegalStateException("Error writing to command socket", ioe); } @@ -384,15 +441,26 @@ class ZygoteConnection { } } - private void handleHiddenApiAccessLogSampleRate(int samplingRate) { - try { + /** + * Changes the API access log sample rate for the Zygote and processes spawned from it. + * + * This necessitates a change to the internal state of the Zygote. As such, if the blastula + * pool is enabled all existing blastulas have an incorrect API access log sample rate. To + * properly handle this request the pool must be emptied and refilled. This process can return + * a Runnable object that must be returned to ZygoteServer.runSelectLoop to be invoked. + * + * @param zygoteServer The server object that received the request + * @param samplingRate The new sample rate + * @return A Runnable object representing a new app in any blastulas spawned from here; the + * zygote process will always receive a null value from this function. + */ + private Runnable handleHiddenApiAccessLogSampleRate(ZygoteServer zygoteServer, + int samplingRate) { + return stateChangeWithBlastulaPoolReset(zygoteServer, () -> { ZygoteInit.setHiddenApiAccessLogSampleRate(samplingRate); HiddenApiUsageLogger.setHiddenApiAccessLogSampleRate(samplingRate); ZygoteInit.setHiddenApiUsageLogger(HiddenApiUsageLogger.getInstance()); - mSocketOutStream.writeInt(0); - } catch (IOException ioe) { - throw new IllegalStateException("Error writing to command socket", ioe); - } + }); } protected void preload() { diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index 2c17540eb6c6..c4c98baf0e4c 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -68,6 +68,15 @@ class ZygoteServer { private static final String BLASTULA_POOL_SIZE_MIN_DEFAULT = "1"; /** + * Indicates if this Zygote server can support a blastula pool. Currently this should only be + * true for the primary and secondary Zygotes, and not the App Zygotes or the WebView Zygote. + * + * TODO (chriswailes): Make this an explicit argument to the constructor + */ + + private final boolean mBlastulaPoolSupported; + + /** * 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 @@ -127,6 +136,8 @@ class ZygoteServer { mBlastulaPoolEventFD = null; mZygoteSocket = null; mBlastulaPoolSocket = null; + + mBlastulaPoolSupported = false; } /** @@ -151,12 +162,18 @@ class ZygoteServer { } fetchBlastulaPoolPolicyProps(); + + mBlastulaPoolSupported = true; } void setForkChild() { mIsForkChild = true; } + public boolean isBlastulaPoolEnabled() { + return mBlastulaPoolEnabled; + } + /** * Registers a server socket for zygote command connections. This opens the server socket * at the specified name in the abstract socket namespace. @@ -224,42 +241,43 @@ class ZygoteServer { } private void fetchBlastulaPoolPolicyProps() { - final String blastulaPoolSizeMaxPropString = - Zygote.getSystemProperty( - DeviceConfig.RuntimeNative.BLASTULA_POOL_SIZE_MAX, - BLASTULA_POOL_SIZE_MAX_DEFAULT); - - if (!blastulaPoolSizeMaxPropString.isEmpty()) { - mBlastulaPoolSizeMax = - Integer.min( - Integer.parseInt(blastulaPoolSizeMaxPropString), - BLASTULA_POOL_SIZE_MAX_LIMIT); - } + if (mBlastulaPoolSupported) { + final String blastulaPoolSizeMaxPropString = + Zygote.getSystemProperty( + DeviceConfig.RuntimeNative.BLASTULA_POOL_SIZE_MAX, + BLASTULA_POOL_SIZE_MAX_DEFAULT); + + if (!blastulaPoolSizeMaxPropString.isEmpty()) { + mBlastulaPoolSizeMax = + Integer.min( + Integer.parseInt(blastulaPoolSizeMaxPropString), + BLASTULA_POOL_SIZE_MAX_LIMIT); + } - final String blastulaPoolSizeMinPropString = - Zygote.getSystemProperty( - DeviceConfig.RuntimeNative.BLASTULA_POOL_SIZE_MIN, - BLASTULA_POOL_SIZE_MIN_DEFAULT); + final String blastulaPoolSizeMinPropString = + Zygote.getSystemProperty( + DeviceConfig.RuntimeNative.BLASTULA_POOL_SIZE_MIN, + BLASTULA_POOL_SIZE_MIN_DEFAULT); - if (!blastulaPoolSizeMinPropString.isEmpty()) { - mBlastulaPoolSizeMin = - Integer.max( - Integer.parseInt(blastulaPoolSizeMinPropString), - BLASTULA_POOL_SIZE_MIN_LIMIT); - } + if (!blastulaPoolSizeMinPropString.isEmpty()) { + mBlastulaPoolSizeMin = + Integer.max( + Integer.parseInt(blastulaPoolSizeMinPropString), + BLASTULA_POOL_SIZE_MIN_LIMIT); + } - final String blastulaPoolRefillThresholdPropString = - Zygote.getSystemProperty( - DeviceConfig.RuntimeNative.BLASTULA_POOL_REFILL_THRESHOLD, - Integer.toString(mBlastulaPoolSizeMax / 2)); + final String blastulaPoolRefillThresholdPropString = + Zygote.getSystemProperty( + DeviceConfig.RuntimeNative.BLASTULA_POOL_REFILL_THRESHOLD, + Integer.toString(mBlastulaPoolSizeMax / 2)); - if (!blastulaPoolRefillThresholdPropString.isEmpty()) { - mBlastulaPoolRefillThreshold = - Integer.min( - Integer.parseInt(blastulaPoolRefillThresholdPropString), - mBlastulaPoolSizeMax); + if (!blastulaPoolRefillThresholdPropString.isEmpty()) { + mBlastulaPoolRefillThreshold = + Integer.min( + Integer.parseInt(blastulaPoolRefillThresholdPropString), + mBlastulaPoolSizeMax); + } } - } private long mLastPropCheckTimestamp = 0; @@ -282,44 +300,65 @@ class ZygoteServer { * this function will return a Runnable object representing the new application that is * passed up from blastulaMain. */ - private Runnable fillBlastulaPool(int[] sessionSocketRawFDs) { - if (mBlastulaPoolEnabled) { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool"); - int blastulaPoolCount = Zygote.getBlastulaPoolCount(); - int numBlastulasToSpawn = mBlastulaPoolSizeMax - blastulaPoolCount; + Runnable fillBlastulaPool(int[] sessionSocketRawFDs) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillBlastulaPool"); - if (blastulaPoolCount < mBlastulaPoolSizeMin - || numBlastulasToSpawn >= mBlastulaPoolRefillThreshold) { + int blastulaPoolCount = Zygote.getBlastulaPoolCount(); + int numBlastulasToSpawn = mBlastulaPoolSizeMax - blastulaPoolCount; - // Disable some VM functionality and reset some system values - // before forking. - ZygoteHooks.preFork(); - Zygote.resetNicePriority(); + if (blastulaPoolCount < mBlastulaPoolSizeMin + || numBlastulasToSpawn >= mBlastulaPoolRefillThreshold) { - while (blastulaPoolCount++ < mBlastulaPoolSizeMax) { - Runnable caller = Zygote.forkBlastula(mBlastulaPoolSocket, sessionSocketRawFDs); + // Disable some VM functionality and reset some system values + // before forking. + ZygoteHooks.preFork(); + Zygote.resetNicePriority(); - if (caller != null) { - return caller; - } - } - - // Re-enable runtime services for the Zygote. Blastula services - // are re-enabled in specializeBlastula. - ZygoteHooks.postForkCommon(); + while (blastulaPoolCount++ < mBlastulaPoolSizeMax) { + Runnable caller = Zygote.forkBlastula(mBlastulaPoolSocket, sessionSocketRawFDs); - Log.i("zygote", - "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn); + if (caller != null) { + return caller; + } } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + // Re-enable runtime services for the Zygote. Blastula services + // are re-enabled in specializeBlastula. + ZygoteHooks.postForkCommon(); + + Log.i("zygote", + "Filled the blastula pool. New blastulas: " + numBlastulasToSpawn); } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + return null; } /** + * Empty or fill the blastula pool as dictated by the current and new blastula pool statuses. + */ + Runnable setBlastulaPoolStatus(boolean newStatus, LocalSocket sessionSocket) { + if (!mBlastulaPoolSupported) { + Log.w(TAG, + "Attempting to enable a blastula pool for a Zygote that doesn't support it."); + return null; + } else if (mBlastulaPoolEnabled == newStatus) { + return null; + } + + mBlastulaPoolEnabled = newStatus; + + if (newStatus) { + return fillBlastulaPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() }); + } else { + Zygote.emptyBlastulaPool(); + return null; + } + } + + /** * Runs the zygote process's select loop. Accepts new connections as * they happen, and reads commands from connections one spawn-request's * worth at a time. @@ -334,12 +373,26 @@ class ZygoteServer { while (true) { fetchBlastulaPoolPolicyPropsWithMinInterval(); - int[] blastulaPipeFDs = Zygote.getBlastulaPipeFDs(); + int[] blastulaPipeFDs = null; + StructPollfd[] pollFDs = null; + + // Allocate enough space for the poll structs, taking into account + // the state of the blastula pool for this Zygote (could be a + // regular Zygote, a WebView Zygote, or an AppZygote). + if (mBlastulaPoolEnabled) { + blastulaPipeFDs = Zygote.getBlastulaPipeFDs(); + pollFDs = new StructPollfd[socketFDs.size() + 1 + blastulaPipeFDs.length]; + } else { + pollFDs = new StructPollfd[socketFDs.size()]; + } - // 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]; + /* + * For reasons of correctness the blastula pool pipe and event FDs + * must be processed before the session and server sockets. This + * is to ensure that the blastula pool accounting information is + * accurate when handling other requests like API blacklist + * exemptions. + */ int pollIndex = 0; for (FileDescriptor socketFD : socketFDs) { @@ -350,19 +403,22 @@ class ZygoteServer { } final int blastulaPoolEventFDIndex = pollIndex; - pollFDs[pollIndex] = new StructPollfd(); - pollFDs[pollIndex].fd = mBlastulaPoolEventFD; - pollFDs[pollIndex].events = (short) POLLIN; - ++pollIndex; - - for (int blastulaPipeFD : blastulaPipeFDs) { - FileDescriptor managedFd = new FileDescriptor(); - managedFd.setInt$(blastulaPipeFD); + if (mBlastulaPoolEnabled) { pollFDs[pollIndex] = new StructPollfd(); - pollFDs[pollIndex].fd = managedFd; + pollFDs[pollIndex].fd = mBlastulaPoolEventFD; 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 { @@ -371,6 +427,8 @@ class ZygoteServer { throw new RuntimeException("poll failed", ex); } + boolean blastulaPoolFDRead = false; + while (--pollIndex >= 0) { if ((pollFDs[pollIndex].revents & POLLIN) == 0) { continue; @@ -390,6 +448,7 @@ class ZygoteServer { ZygoteConnection connection = peers.get(pollIndex); final Runnable command = connection.processOneCommand(this); + // TODO (chriswailes): Is this extra check necessary? if (mIsForkChild) { // We're in the child. We should always have a command to run at this // stage if processOneCommand hasn't called "exec". @@ -480,17 +539,22 @@ class ZygoteServer { Zygote.removeBlastulaTableEntry((int) messagePayload); } - int[] sessionSocketRawFDs = - socketFDs.subList(1, socketFDs.size()) + blastulaPoolFDRead = true; + } + } + + // Check to see if the blastula pool needs to be refilled. + if (blastulaPoolFDRead) { + int[] sessionSocketRawFDs = + socketFDs.subList(1, socketFDs.size()) .stream() .mapToInt(fd -> fd.getInt$()) .toArray(); - final Runnable command = fillBlastulaPool(sessionSocketRawFDs); + final Runnable command = fillBlastulaPool(sessionSocketRawFDs); - if (command != null) { - return command; - } + 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 656fdcf0f141..7443c36a5a56 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -155,10 +155,10 @@ static std::atomic_uint32_t gBlastulaPoolCount = 0; static int gBlastulaPoolEventFD = -1; /** - * The maximum value that the gBlastulaPoolMax variable may take. This value - * is a mirror of Zygote.BLASTULA_POOL_MAX_LIMIT + * The maximum value that the gBlastulaPoolSizeMax variable may take. This value + * is a mirror of ZygoteServer.BLASTULA_POOL_SIZE_MAX_LIMIT */ -static constexpr int BLASTULA_POOL_MAX_LIMIT = 10; +static constexpr int BLASTULA_POOL_SIZE_MAX_LIMIT = 100; /** * A helper class containing accounting information for Blastulas. @@ -216,6 +216,19 @@ class BlastulaTableEntry { } } + void Clear() { + EntryStorage storage = mStorage.load(); + + if (storage != INVALID_ENTRY_VALUE) { + close(storage.read_pipe_fd); + mStorage.store(INVALID_ENTRY_VALUE); + } + } + + void Invalidate() { + mStorage.store(INVALID_ENTRY_VALUE); + } + /** * @return A copy of the data stored in this entry. */ @@ -257,7 +270,7 @@ class BlastulaTableEntry { * the BlastulaTableEntry class prevent data races during these concurrent * operations. */ -static std::array<BlastulaTableEntry, BLASTULA_POOL_MAX_LIMIT> gBlastulaTable; +static std::array<BlastulaTableEntry, BLASTULA_POOL_SIZE_MAX_LIMIT> gBlastulaTable; /** * The list of open zygote file descriptors. @@ -1168,6 +1181,14 @@ static void UnblockSignal(int signum, fail_fn_t fail_fn) { } } +static void ClearBlastulaTable() { + for (BlastulaTableEntry& entry : gBlastulaTable) { + entry.Clear(); + } + + gBlastulaPoolCount = 0; +} + // Utility routine to fork a process from the zygote. static pid_t ForkCommon(JNIEnv* env, bool is_system_server, const std::vector<int>& fds_to_close, @@ -1210,6 +1231,9 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // Clean up any descriptors which must be closed immediately DetachDescriptors(env, fds_to_close, fail_fn); + // Invalidate the entries in the blastula table. + ClearBlastulaTable(); + // Re-open all remaining open file descriptors so that they aren't shared // with the zygote across a fork. gOpenFdTable->ReopenOrDetach(fail_fn); @@ -1892,6 +1916,27 @@ static jint com_android_internal_os_Zygote_nativeGetBlastulaPoolCount(JNIEnv* en return gBlastulaPoolCount; } +/** + * Kills all processes currently in the blastula pool. + * + * @param env Managed runtime environment + * @return The number of blastulas currently in the blastula pool + */ +static void com_android_internal_os_Zygote_nativeEmptyBlastulaPool(JNIEnv* env, jclass) { + for (auto& entry : gBlastulaTable) { + auto entry_storage = entry.GetValues(); + + if (entry_storage.has_value()) { + kill(entry_storage.value().pid, SIGKILL); + close(entry_storage.value().read_pipe_fd); + + // Avoid a second atomic load by invalidating instead of clearing. + entry.Invalidate(); + --gBlastulaPoolCount; + } + } +} + static const JNINativeMethod gMethods[] = { { "nativeSecurityInit", "()V", (void *) com_android_internal_os_Zygote_nativeSecurityInit }, @@ -1922,7 +1967,9 @@ static const JNINativeMethod gMethods[] = { { "nativeGetBlastulaPoolEventFD", "()I", (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolEventFD }, { "nativeGetBlastulaPoolCount", "()I", - (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount } + (void *) com_android_internal_os_Zygote_nativeGetBlastulaPoolCount }, + { "nativeEmptyBlastulaPool", "()V", + (void *) com_android_internal_os_Zygote_nativeEmptyBlastulaPool } }; int register_com_android_internal_os_Zygote(JNIEnv* env) { |