Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b3215002 authored by Chris Wailes's avatar Chris Wailes
Browse files

Adjusts the USAP pool refill mechanism.

This patch adjusts the USAP pool refill mechanism so that it is less
disruptive to launching applications.  Refill events are now delayed
by several (3) seconds if delaying wouldn't cause the pool to be below
the minimum population size.

This patch also adjusts the process priorities for the USAPs and other
processes spawned from the Zygote so that they behave better in the
context they are spawned in.

Bug: 131362095
Test: booted & launched apps & checked logs
Change-Id: If26d625c4b1e5e8eee54dcdacb32360b0d852829
parent 4e7c6b90
Loading
Loading
Loading
Loading
+30 −17
Original line number Diff line number Diff line
@@ -172,6 +172,11 @@ public final class Zygote {
     */
    public static final int SOCKET_BUFFER_SIZE = 256;

    /**
     * @hide for internal use only
     */
    private static final int PRIORITY_MAX = -20;

    /** a prototype instance for a future List.toArray() */
    static final int[][] INT_ARRAY_2D = new int[0][0];

@@ -236,8 +241,7 @@ public final class Zygote {
            int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
            int targetSdkVersion) {
        ZygoteHooks.preFork();
        // Resets nice priority for zygote process.
        resetNicePriority();

        int pid = nativeForkAndSpecialize(
                uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                fdsToIgnore, startChildZygote, instructionSet, appDataDir);
@@ -249,6 +253,7 @@ public final class Zygote {
            // Note that this event ends at the end of handleChildProc,
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
        }

        ZygoteHooks.postForkCommon();
        return pid;
    }
@@ -335,15 +340,16 @@ public final class Zygote {
    static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
            int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
        ZygoteHooks.preFork();
        // Resets nice priority for zygote process.
        resetNicePriority();

        int pid = nativeForkSystemServer(
                uid, gid, gids, runtimeFlags, rlimits,
                permittedCapabilities, effectiveCapabilities);

        // Enable tracing as soon as we enter the system_server.
        if (pid == 0) {
            Trace.setTracingEnabled(true, runtimeFlags);
        }

        ZygoteHooks.postForkCommon();
        return pid;
    }
@@ -461,13 +467,16 @@ public final class Zygote {
    /**
     * Fork a new unspecialized app process from the zygote
     *
     * @param usapPoolSocket  The server socket the USAP will call accept on
     * @param sessionSocketRawFDs  Anonymous session sockets that are currently open
     * @param isPriorityFork  Value controlling the process priority level until accept is called
     * @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.
     */
    static Runnable forkUsap(LocalServerSocket usapPoolSocket,
                             int[] sessionSocketRawFDs) {
                             int[] sessionSocketRawFDs,
                             boolean isPriorityFork) {
        FileDescriptor[] pipeFDs = null;

        try {
@@ -477,7 +486,8 @@ public final class Zygote {
        }

        int pid =
                nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), sessionSocketRawFDs);
                nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(),
                               sessionSocketRawFDs, isPriorityFork);

        if (pid == 0) {
            IoUtils.closeQuietly(pipeFDs[0]);
@@ -492,7 +502,8 @@ public final class Zygote {

    private static native int nativeForkUsap(int readPipeFD,
                                             int writePipeFD,
                                                 int[] sessionSocketRawFDs);
                                             int[] sessionSocketRawFDs,
                                             boolean isPriorityFork);

    /**
     * This function is used by unspecialized app processes to wait for specialization requests from
@@ -515,6 +526,11 @@ public final class Zygote {
        // Load resources
        ZygoteInit.nativePreloadGraphicsDriver();

        // Change the priority to max before calling accept so we can respond to new specialization
        // requests as quickly as possible.  This will be reverted to the default priority in the
        // native specialization code.
        boostUsapPriority();

        while (true) {
            try {
                sessionSocket = usapPoolSocket.accept();
@@ -617,6 +633,12 @@ public final class Zygote {
                                     null /* classLoader */);
    }

    private static void boostUsapPriority() {
        nativeBoostUsapPriority();
    }

    private static native void nativeBoostUsapPriority();

    static void setAppProcessName(ZygoteArguments args, String loggingTag) {
        if (args.mNiceName != null) {
            Process.setArgV0(args.mNiceName);
@@ -872,15 +894,6 @@ public final class Zygote {
        ZygoteHooks.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet);
    }

    /**
     * Resets the calling thread priority to the default value (Thread.NORM_PRIORITY
     * or nice value 0). This updates both the priority value in java.lang.Thread and
     * the nice value (setpriority).
     */
    static void resetNicePriority() {
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
    }

    /**
     * Executes "/system/bin/sh -c <command>" using the exec() system call.
     * This method throws a runtime exception if exec() failed, otherwise, this
+1 −1
Original line number Diff line number Diff line
@@ -330,7 +330,7 @@ class ZygoteConnection {
            if (zygoteServer.isUsapPoolEnabled()) {
                Runnable fpResult =
                        zygoteServer.fillUsapPool(
                                new int[]{mSocket.getFileDescriptor().getInt$()});
                                new int[]{mSocket.getFileDescriptor().getInt$()}, false);

                if (fpResult != null) {
                    zygoteServer.setForkChild();
+3 −2
Original line number Diff line number Diff line
@@ -822,6 +822,9 @@ public class ZygoteInit {
    public static void main(String argv[]) {
        ZygoteServer zygoteServer = null;

        // Set the initial thread priority to the "normal" value.
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

        // Mark zygote start. This ensures that thread creation will throw
        // an error.
        ZygoteHooks.startZygoteNoThreadCreation();
@@ -881,8 +884,6 @@ public class ZygoteInit {
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                        SystemClock.uptimeMillis());
                bootTimingsTraceLog.traceEnd(); // ZygotePreload
            } else {
                Zygote.resetNicePriority();
            }

            // Do an initial gc to clean up after startup
+206 −125
Original line number Diff line number Diff line
@@ -66,6 +66,15 @@ class ZygoteServer {
    /** The default value used for the USAP_POOL_SIZE_MIN device property */
    private static final String USAP_POOL_SIZE_MIN_DEFAULT = "1";

    /**
     * Number of milliseconds to delay before refilling the pool if it hasn't reached its
     * minimum value.
     */
    private static final int USAP_REFILL_DELAY_MS = 3000;

    /** The "not a timestamp" value for the refill delay timestamp mechanism. */
    private static final int INVALID_TIMESTAMP = -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
@@ -131,6 +140,12 @@ class ZygoteServer {
     */
    private int mUsapPoolRefillThreshold = 0;

    private enum UsapPoolRefillAction {
        DELAYED,
        IMMEDIATE,
        NONE
    }

    ZygoteServer() {
        mUsapPoolEventFD = null;
        mZygoteSocket = null;
@@ -293,9 +308,16 @@ class ZygoteServer {
        }
    }

    private void fetchUsapPoolPolicyPropsIfUnfetched() {
        if (mIsFirstPropertyCheck) {
            mIsFirstPropertyCheck = false;
            fetchUsapPoolPolicyProps();
        }
    }

    /**
     * Checks to see if the current policy says that pool should be refilled, and spawns new USAPs
     * if necessary.
     * Refill the USAP Pool to the appropriate level, determined by whether this is a priority
     * refill event or not.
     *
     * @param sessionSocketRawFDs  Anonymous session sockets that are currently open
     * @return In the Zygote process this function will always return null; in unspecialized app
@@ -303,25 +325,36 @@ class ZygoteServer {
     *         application that is passed up from usapMain.
     */

    Runnable fillUsapPool(int[] sessionSocketRawFDs) {
    Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillUsapPool");

        // Ensure that the pool properties have been fetched.
        fetchUsapPoolPolicyPropsWithMinInterval();
        fetchUsapPoolPolicyPropsIfUnfetched();

        int usapPoolCount = Zygote.getUsapPoolCount();
        int numUsapsToSpawn = mUsapPoolSizeMax - usapPoolCount;
        int numUsapsToSpawn;

        if (isPriorityRefill) {
            // Refill to min
            numUsapsToSpawn = mUsapPoolSizeMin - usapPoolCount;

        if (usapPoolCount < mUsapPoolSizeMin
                || numUsapsToSpawn >= mUsapPoolRefillThreshold) {
            Log.i("zygote",
                    "Priority USAP Pool refill. New USAPs: " + numUsapsToSpawn);
        } else {
            // Refill up to max
            numUsapsToSpawn = mUsapPoolSizeMax - usapPoolCount;

            Log.i("zygote",
                    "Delayed USAP Pool refill. New USAPs: " + numUsapsToSpawn);
        }

        // Disable some VM functionality and reset some system values
        // before forking.
        ZygoteHooks.preFork();
            Zygote.resetNicePriority();

            while (usapPoolCount++ < mUsapPoolSizeMax) {
                Runnable caller = Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs);
        while (--numUsapsToSpawn >= 0) {
            Runnable caller =
                    Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs, isPriorityRefill);

            if (caller != null) {
                return caller;
@@ -332,10 +365,6 @@ class ZygoteServer {
        // 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;
@@ -358,7 +387,7 @@ class ZygoteServer {
        mUsapPoolEnabled = newStatus;

        if (newStatus) {
            return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() });
            return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() }, false);
        } else {
            Zygote.emptyUsapPool();
            return null;
@@ -377,6 +406,8 @@ class ZygoteServer {
        socketFDs.add(mZygoteSocket.getFileDescriptor());
        peers.add(null);

        long usapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;

        while (true) {
            fetchUsapPoolPolicyPropsWithMinInterval();

@@ -430,12 +461,40 @@ class ZygoteServer {
                }
            }

            int pollTimeoutMs;

            if (usapPoolRefillTriggerTimestamp == INVALID_TIMESTAMP) {
                pollTimeoutMs = -1;
            } else {
                int elapsedTimeMs =
                        (int) (System.currentTimeMillis() - usapPoolRefillTriggerTimestamp);

                if (elapsedTimeMs >= USAP_REFILL_DELAY_MS) {
                    // Normalize the poll timeout value when the time between one poll event and the
                    // next pushes us over the delay value.  This prevents poll receiving a 0
                    // timeout value, which would result in it returning immediately.
                    pollTimeoutMs = -1;
                } else {
                    pollTimeoutMs = USAP_REFILL_DELAY_MS - elapsedTimeMs;
                }
            }

            int pollReturnValue;
            try {
                Os.poll(pollFDs, -1);
                pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }

            UsapPoolRefillAction usapPoolRefillAction = UsapPoolRefillAction.NONE;
            if (pollReturnValue == 0) {
                // The poll timeout has been exceeded.  This only occurs when we have finished the
                // USAP pool refill delay period.

                usapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
                usapPoolRefillAction = UsapPoolRefillAction.DELAYED;

            } else {
                boolean usapPoolFDRead = false;

                while (--pollIndex >= 0) {
@@ -459,8 +518,8 @@ class ZygoteServer {

                            // 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".
                                // 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");
                                }
@@ -474,7 +533,8 @@ 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.
                                // shows up as a regular POLLIN event in our regular processing
                                // loop.
                                if (connection.isClosedByPeer()) {
                                    connection.closeSocket();
                                    peers.remove(pollIndex);
@@ -483,10 +543,10 @@ class ZygoteServer {
                            }
                        } 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.
                                // 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);

@@ -499,15 +559,16 @@ class ZygoteServer {
                                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.
                                // 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.
                            // zygote. The flag will not be consulted this loop pass after the
                            // Runnable is returned.
                            mIsForkChild = false;
                        }
                    } else {
@@ -516,13 +577,14 @@ class ZygoteServer {
                        // 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.  The `continue` statements below ensure that
                    // the messagePayload will always be valid if we complete the try block without
                    // an exception.
                        // the messagePayload will always be valid if we complete the try block
                        // without an exception.
                        long messagePayload;

                        try {
                            byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
                        int readBytes = Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
                            int readBytes =
                                    Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);

                            if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
                                DataInputStream inputStream =
@@ -554,18 +616,37 @@ class ZygoteServer {
                    }
                }

            // Check to see if the USAP pool needs to be refilled.
                if (usapPoolFDRead) {
                    int usapPoolCount = Zygote.getUsapPoolCount();

                    if (usapPoolCount < mUsapPoolSizeMin) {
                        // Immediate refill
                        usapPoolRefillAction = UsapPoolRefillAction.IMMEDIATE;
                    } else if (mUsapPoolSizeMax - usapPoolCount >= mUsapPoolRefillThreshold) {
                        // Delayed refill
                        usapPoolRefillTriggerTimestamp = System.currentTimeMillis();
                    }
                }
            }

            if (usapPoolRefillAction != UsapPoolRefillAction.NONE) {
                int[] sessionSocketRawFDs =
                        socketFDs.subList(1, socketFDs.size())
                                .stream()
                                .mapToInt(FileDescriptor::getInt$)
                                .toArray();

                final Runnable command = fillUsapPool(sessionSocketRawFDs);
                final boolean isPriorityRefill =
                        usapPoolRefillAction == UsapPoolRefillAction.IMMEDIATE;

                final Runnable command =
                        fillUsapPool(sessionSocketRawFDs, isPriorityRefill);

                if (command != null) {
                    return command;
                } else if (isPriorityRefill) {
                    // Schedule a delayed refill to finish refilling the pool.
                    usapPoolRefillTriggerTimestamp = System.currentTimeMillis();
                }
            }
        }
+36 −7
Original line number Diff line number Diff line
@@ -163,6 +163,15 @@ static int gUsapPoolEventFD = -1;
 */
static constexpr int USAP_POOL_SIZE_MAX_LIMIT = 100;

/** The numeric value for the maximum priority a process may possess. */
static constexpr int PROCESS_PRIORITY_MAX = -20;

/** The numeric value for the minimum priority a process may possess. */
static constexpr int PROCESS_PRIORITY_MIN = 19;

/** The numeric value for the normal priority a process should have. */
static constexpr int PROCESS_PRIORITY_DEFAULT = 0;

/**
 * A helper class containing accounting information for USAPs.
 */
@@ -887,7 +896,8 @@ static void ClearUsapTable() {
// 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,
                        const std::vector<int>& fds_to_ignore) {
                        const std::vector<int>& fds_to_ignore,
                        bool is_priority_fork) {
  SetSignalHandlers();

  // Curry a failure function.
@@ -920,6 +930,12 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
  pid_t pid = fork();

  if (pid == 0) {
    if (is_priority_fork) {
      setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
    } else {
      setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);
    }

    // The child process.
    PreApplicationInit();

@@ -1117,6 +1133,9 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
  env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags,
                            is_system_server, is_child_zygote, managed_instruction_set);

  // Reset the process priority to the default value.
  setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_DEFAULT);

  if (env->ExceptionCheck()) {
    fail_fn("Error calling post fork hooks.");
  }
@@ -1368,7 +1387,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
      fds_to_ignore.push_back(gUsapPoolEventFD);
    }

    pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore);
    pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore, true);

    if (pid == 0) {
      SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
@@ -1395,7 +1414,8 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(

  pid_t pid = ForkCommon(env, true,
                         fds_to_close,
                         fds_to_ignore);
                         fds_to_ignore,
                         true);
  if (pid == 0) {
      SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                       permitted_capabilities, effective_capabilities,
@@ -1437,13 +1457,15 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
 * zygote in managed code.
 * @param managed_session_socket_fds  A list of anonymous session sockets that must be ignored by
 * the FD hygiene code and automatically "closed" in the new USAP.
 * @param is_priority_fork  Controls the nice level assigned to the newly created process
 * @return
 */
static jint com_android_internal_os_Zygote_nativeForkUsap(JNIEnv* env,
                                                          jclass,
                                                          jint read_pipe_fd,
                                                          jint write_pipe_fd,
                                                          jintArray managed_session_socket_fds) {
                                                          jintArray managed_session_socket_fds,
                                                          jboolean is_priority_fork) {
  std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()),
                   fds_to_ignore(fds_to_close);

@@ -1465,7 +1487,8 @@ static jint com_android_internal_os_Zygote_nativeForkUsap(JNIEnv* env,
  fds_to_ignore.push_back(write_pipe_fd);
  fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end());

  pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore);
  pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore,
                              is_priority_fork == JNI_TRUE);

  if (usap_pid != 0) {
    ++gUsapPoolCount;
@@ -1669,6 +1692,10 @@ static jboolean com_android_internal_os_Zygote_nativeDisableExecuteOnly(JNIEnv*
  return dl_iterate_phdr(DisableExecuteOnly, nullptr) == 0;
}

static void com_android_internal_os_Zygote_nativeBoostUsapPriority(JNIEnv* env, jclass) {
  setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);
}

static const JNINativeMethod gMethods[] = {
    { "nativeForkAndSpecialize",
      "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
@@ -1681,7 +1708,7 @@ static const JNINativeMethod gMethods[] = {
      (void *) com_android_internal_os_Zygote_nativePreApplicationInit },
    { "nativeInstallSeccompUidGidFilter", "(II)V",
      (void *) com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter },
    { "nativeForkUsap", "(II[I)I",
    { "nativeForkUsap", "(II[IZ)I",
      (void *) com_android_internal_os_Zygote_nativeForkUsap },
    { "nativeSpecializeAppProcess",
      "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
@@ -1699,7 +1726,9 @@ static const JNINativeMethod gMethods[] = {
    { "nativeEmptyUsapPool", "()V",
      (void *) com_android_internal_os_Zygote_nativeEmptyUsapPool },
    { "nativeDisableExecuteOnly", "()Z",
      (void *) com_android_internal_os_Zygote_nativeDisableExecuteOnly }
      (void *) com_android_internal_os_Zygote_nativeDisableExecuteOnly },
    { "nativeBoostUsapPriority", "()V",
      (void* ) com_android_internal_os_Zygote_nativeBoostUsapPriority }
};

int register_com_android_internal_os_Zygote(JNIEnv* env) {