blob: 5d1911b9c29a288d2ad488248df91c101d6f67c1 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import static android.system.OsConstants.POLLIN;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.SystemClock;
import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructPollfd;
import android.util.Log;
import android.util.Slog;
import dalvik.system.ZygoteHooks;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.ArrayList;
/**
* Server socket class for zygote processes.
*
* Provides functions to wait for commands on a UNIX domain socket, and fork
* off child processes that inherit the initial state of the VM.%
*
* Please see {@link ZygoteArguments} for documentation on the
* client protocol.
*/
class ZygoteServer {
// TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
public static final String TAG = "ZygoteServer";
/**
* The maximim value that will be accepted from the USAP_POOL_SIZE_MAX device property.
* is a mirror of USAP_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
*/
private static final int USAP_POOL_SIZE_MAX_LIMIT = 100;
/**
* The minimum value that will be accepted from the USAP_POOL_SIZE_MIN device property.
*/
private static final int USAP_POOL_SIZE_MIN_LIMIT = 1;
/** The default value used for the USAP_POOL_SIZE_MAX device property */
private static final String USAP_POOL_SIZE_MAX_DEFAULT = "10";
/** The default value used for the USAP_POOL_SIZE_MIN device property */
private static final String USAP_POOL_SIZE_MIN_DEFAULT = "1";
/**
* Indicates if this Zygote server can support a unspecialized app process 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 mUsapPoolSupported;
/**
* If the unspecialized app process pool should be created and used to start applications.
*
* Setting this value to false will disable the creation, maintenance, and use of the USAP
* pool. When the USAP pool is disabled the application lifecycle will be identical to
* previous versions of Android.
*/
private boolean mUsapPoolEnabled = false;
/**
* Listening socket that accepts new server connections.
*/
private LocalServerSocket mZygoteSocket;
/**
* The name of the unspecialized app process pool socket to use if the USAP pool is enabled.
*/
private LocalServerSocket mUsapPoolSocket;
/**
* File descriptor used for communication between the signal handler and the ZygoteServer poll
* loop.
* */
private FileDescriptor mUsapPoolEventFD;
/**
* 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.
*/
private boolean mCloseSocketFd;
/**
* Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
*/
private boolean mIsForkChild;
/**
* The runtime-adjustable maximum USAP pool size.
*/
private int mUsapPoolSizeMax = 0;
/**
* The runtime-adjustable minimum USAP pool size.
*/
private int mUsapPoolSizeMin = 0;
/**
* The runtime-adjustable value used to determine when to re-fill the USAP pool. The pool will
* be re-filled when (mUsapPoolMax - gUsapPoolCount) >= sUsapPoolRefillThreshold.
*/
private int mUsapPoolRefillThreshold = 0;
ZygoteServer() {
mUsapPoolEventFD = null;
mZygoteSocket = null;
mUsapPoolSocket = null;
mUsapPoolSupported = false;
}
/**
* Initialize the Zygote server with the Zygote server socket, USAP pool server socket, and USAP
* pool event FD.
*
* @param isPrimaryZygote If this is the primary Zygote or not.
*/
ZygoteServer(boolean isPrimaryZygote) {
mUsapPoolEventFD = Zygote.getUsapPoolEventFD();
if (isPrimaryZygote) {
mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);
mUsapPoolSocket =
Zygote.createManagedSocketFromInitSocket(
Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);
} else {
mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME);
mUsapPoolSocket =
Zygote.createManagedSocketFromInitSocket(
Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);
}
fetchUsapPoolPolicyProps();
mUsapPoolSupported = true;
}
void setForkChild() {
mIsForkChild = true;
}
public boolean isUsapPoolEnabled() {
return mUsapPoolEnabled;
}
/**
* Registers a server socket for zygote command connections. This opens the server socket
* at the specified name in the abstract socket namespace.
*/
void registerServerSocketAtAbstractName(String socketName) {
if (mZygoteSocket == null) {
try {
mZygoteSocket = new LocalServerSocket(socketName);
mCloseSocketFd = false;
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to abstract socket '" + socketName + "'", ex);
}
}
}
/**
* Waits for and accepts a single command connection. Throws
* RuntimeException on failure.
*/
private ZygoteConnection acceptCommandPeer(String abiList) {
try {
return createNewConnection(mZygoteSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
}
protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
throws IOException {
return new ZygoteConnection(socket, abiList);
}
/**
* Close and clean up zygote sockets. Called on shutdown and on the
* child's exit path.
*/
void closeServerSocket() {
try {
if (mZygoteSocket != null) {
FileDescriptor fd = mZygoteSocket.getFileDescriptor();
mZygoteSocket.close();
if (fd != null && mCloseSocketFd) {
Os.close(fd);
}
}
} catch (IOException ex) {
Log.e(TAG, "Zygote: error closing sockets", ex);
} catch (ErrnoException ex) {
Log.e(TAG, "Zygote: error closing descriptor", ex);
}
mZygoteSocket = null;
}
/**
* Return the server socket's underlying file descriptor, so that
* ZygoteConnection can pass it to the native code for proper
* closure after a child process is forked off.
*/
FileDescriptor getZygoteSocketFileDescriptor() {
return mZygoteSocket.getFileDescriptor();
}
private void fetchUsapPoolPolicyProps() {
if (mUsapPoolSupported) {
final String usapPoolSizeMaxPropString = Zygote.getConfigurationProperty(
ZygoteConfig.USAP_POOL_SIZE_MAX, USAP_POOL_SIZE_MAX_DEFAULT);
if (!usapPoolSizeMaxPropString.isEmpty()) {
mUsapPoolSizeMax = Integer.min(Integer.parseInt(
usapPoolSizeMaxPropString), USAP_POOL_SIZE_MAX_LIMIT);
}
final String usapPoolSizeMinPropString = Zygote.getConfigurationProperty(
ZygoteConfig.USAP_POOL_SIZE_MIN, USAP_POOL_SIZE_MIN_DEFAULT);
if (!usapPoolSizeMinPropString.isEmpty()) {
mUsapPoolSizeMin = Integer.max(
Integer.parseInt(usapPoolSizeMinPropString), USAP_POOL_SIZE_MIN_LIMIT);
}
final String usapPoolRefillThresholdPropString = Zygote.getConfigurationProperty(
ZygoteConfig.USAP_POOL_REFILL_THRESHOLD,
Integer.toString(mUsapPoolSizeMax / 2));
if (!usapPoolRefillThresholdPropString.isEmpty()) {
mUsapPoolRefillThreshold = Integer.min(
Integer.parseInt(usapPoolRefillThresholdPropString),
mUsapPoolSizeMax);
}
// Sanity check
if (mUsapPoolSizeMin >= mUsapPoolSizeMax) {
Log.w(TAG, "The max size of the USAP pool must be greater than the minimum size."
+ " Restoring default values.");
mUsapPoolSizeMax = Integer.parseInt(USAP_POOL_SIZE_MAX_DEFAULT);
mUsapPoolSizeMin = Integer.parseInt(USAP_POOL_SIZE_MIN_DEFAULT);
mUsapPoolRefillThreshold = mUsapPoolSizeMax / 2;
}
}
}
private boolean mIsFirstPropertyCheck = true;
private long mLastPropCheckTimestamp = 0;
private void fetchUsapPoolPolicyPropsWithMinInterval() {
final long currentTimestamp = SystemClock.elapsedRealtime();
if (mIsFirstPropertyCheck
|| (currentTimestamp - mLastPropCheckTimestamp >= Zygote.PROPERTY_CHECK_INTERVAL)) {
mIsFirstPropertyCheck = false;
mLastPropCheckTimestamp = currentTimestamp;
fetchUsapPoolPolicyProps();
}
}
/**
* Checks to see if the current policy says that pool should be refilled, and spawns new USAPs
* if necessary.
*
* @param sessionSocketRawFDs Anonymous session sockets that are currently open
* @return In the Zygote process this function will always return null; in unspecialized app
* processes this function will return a Runnable object representing the new
* application that is passed up from usapMain.
*/
Runnable fillUsapPool(int[] sessionSocketRawFDs) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillUsapPool");
// Ensure that the pool properties have been fetched.
fetchUsapPoolPolicyPropsWithMinInterval();
int usapPoolCount = Zygote.getUsapPoolCount();
int numUsapsToSpawn = mUsapPoolSizeMax - usapPoolCount;
if (usapPoolCount < mUsapPoolSizeMin
|| numUsapsToSpawn >= mUsapPoolRefillThreshold) {
// Disable some VM functionality and reset some system values
// before forking.
ZygoteHooks.preFork();
Zygote.resetNicePriority();
while (usapPoolCount++ < mUsapPoolSizeMax) {
Runnable caller = Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs);
if (caller != null) {
return caller;
}
}
// Re-enable runtime services for the Zygote. Services for unspecialized app process
// are re-enabled in specializeAppProcess.
ZygoteHooks.postForkCommon();
Log.i("zygote",
"Filled the USAP pool. New USAPs: " + numUsapsToSpawn);
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return null;
}
/**
* Empty or fill the USAP pool as dictated by the current and new USAP pool statuses.
*/
Runnable setUsapPoolStatus(boolean newStatus, LocalSocket sessionSocket) {
if (!mUsapPoolSupported) {
Log.w(TAG,
"Attempting to enable a USAP pool for a Zygote that doesn't support it.");
return null;
} else if (mUsapPoolEnabled == newStatus) {
return null;
}
Log.i(TAG, "USAP Pool status change: " + (newStatus ? "ENABLED" : "DISABLED"));
mUsapPoolEnabled = newStatus;
if (newStatus) {
return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() });
} else {
Zygote.emptyUsapPool();
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.
*/
Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> socketFDs = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
socketFDs.add(mZygoteSocket.getFileDescriptor());
peers.add(null);
while (true) {
fetchUsapPoolPolicyPropsWithMinInterval();
int[] usapPipeFDs = null;
StructPollfd[] pollFDs = null;
// Allocate enough space for the poll structs, taking into account
// the state of the USAP pool for this Zygote (could be a
// regular Zygote, a WebView Zygote, or an AppZygote).
if (mUsapPoolEnabled) {
usapPipeFDs = Zygote.getUsapPipeFDs();
pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length];
} else {
pollFDs = new StructPollfd[socketFDs.size()];
}
/*
* For reasons of correctness the USAP pool pipe and event FDs
* must be processed before the session and server sockets. This
* is to ensure that the USAP pool accounting information is
* accurate when handling other requests like API blacklist
* exemptions.
*/
int pollIndex = 0;
for (FileDescriptor socketFD : socketFDs) {
pollFDs[pollIndex] = new StructPollfd();
pollFDs[pollIndex].fd = socketFD;
pollFDs[pollIndex].events = (short) POLLIN;
++pollIndex;
}
final int usapPoolEventFDIndex = pollIndex;
if (mUsapPoolEnabled) {
pollFDs[pollIndex] = new StructPollfd();
pollFDs[pollIndex].fd = mUsapPoolEventFD;
pollFDs[pollIndex].events = (short) POLLIN;
++pollIndex;
for (int usapPipeFD : usapPipeFDs) {
FileDescriptor managedFd = new FileDescriptor();
managedFd.setInt$(usapPipeFD);
pollFDs[pollIndex] = new StructPollfd();
pollFDs[pollIndex].fd = managedFd;
pollFDs[pollIndex].events = (short) POLLIN;
++pollIndex;
}
}
try {
Os.poll(pollFDs, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
boolean usapPoolFDRead = false;
while (--pollIndex >= 0) {
if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
continue;
}
if (pollIndex == 0) {
// Zygote server socket
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
socketFDs.add(newPeer.getFileDescriptor());
} else if (pollIndex < usapPoolEventFDIndex) {
// Session socket accepted from the Zygote server socket
try {
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".
if (command == null) {
throw new IllegalStateException("command == null");
}
return command;
} else {
// We're in the server - we should never have any commands to run.
if (command != null) {
throw new IllegalStateException("command != null");
}
// 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.
if (connection.isClosedByPeer()) {
connection.closeSocket();
peers.remove(pollIndex);
socketFDs.remove(pollIndex);
}
}
} catch (Exception e) {
if (!mIsForkChild) {
// We're in the server so any exception here is one that has taken place
// pre-fork while processing commands or reading / writing from the
// control socket. Make a loud noise about any such exceptions so that
// we know exactly what failed and why.
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(pollIndex);
conn.closeSocket();
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()
// method). Log the details of the exception and bring down the process.
Log.e(TAG, "Caught post-fork exception in child process.", e);
throw e;
}
} finally {
// Reset the child flag, in the event that the child process is a child-
// zygote. The flag will not be consulted this loop pass after the Runnable
// is returned.
mIsForkChild = false;
}
} else {
// Either the USAP pool event FD or a USAP reporting pipe.
// If this is the event FD the payload will be the number of USAPs removed.
// If this is a reporting pipe FD the payload will be the PID of the USAP
// that was just specialized.
long messagePayload = -1;
try {
byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
DataInputStream inputStream =
new DataInputStream(new ByteArrayInputStream(buffer));
messagePayload = inputStream.readLong();
} else {
Log.e(TAG, "Incomplete read from USAP management FD of size "
+ readBytes);
continue;
}
} catch (Exception ex) {
if (pollIndex == usapPoolEventFDIndex) {
Log.e(TAG, "Failed to read from USAP pool event FD: "
+ ex.getMessage());
} else {
Log.e(TAG, "Failed to read from USAP reporting pipe: "
+ ex.getMessage());
}
continue;
}
if (pollIndex > usapPoolEventFDIndex) {
Zygote.removeUsapTableEntry((int) messagePayload);
}
usapPoolFDRead = true;
}
}
// Check to see if the USAP pool needs to be refilled.
if (usapPoolFDRead) {
int[] sessionSocketRawFDs =
socketFDs.subList(1, socketFDs.size())
.stream()
.mapToInt(fd -> fd.getInt$())
.toArray();
final Runnable command = fillUsapPool(sessionSocketRawFDs);
if (command != null) {
return command;
}
}
}
}
}