• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.os;
18 
19 import static android.system.OsConstants.POLLIN;
20 
21 import android.net.LocalServerSocket;
22 import android.net.LocalSocket;
23 import android.os.SystemClock;
24 import android.os.Trace;
25 import android.system.ErrnoException;
26 import android.system.Os;
27 import android.system.StructPollfd;
28 import android.util.Log;
29 import android.util.Slog;
30 
31 import dalvik.system.ZygoteHooks;
32 
33 import java.io.ByteArrayInputStream;
34 import java.io.DataInputStream;
35 import java.io.FileDescriptor;
36 import java.io.IOException;
37 import java.util.ArrayList;
38 
39 /**
40  * Server socket class for zygote processes.
41  *
42  * Provides functions to wait for commands on a UNIX domain socket, and fork
43  * off child processes that inherit the initial state of the VM.%
44  *
45  * Please see {@link ZygoteArguments} for documentation on the
46  * client protocol.
47  */
48 class ZygoteServer {
49     // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
50     public static final String TAG = "ZygoteServer";
51 
52     /** The "not a timestamp" value for the refill delay timestamp mechanism. */
53     private static final int INVALID_TIMESTAMP = -1;
54 
55     /**
56      * Indicates if this Zygote server can support a unspecialized app process pool.  Currently this
57      * should only be true for the primary and secondary Zygotes, and not the App Zygotes or the
58      * WebView Zygote.
59      *
60      * TODO (chriswailes): Make this an explicit argument to the constructor
61      */
62 
63     private final boolean mUsapPoolSupported;
64 
65     /**
66      * If the unspecialized app process pool should be created and used to start applications.
67      *
68      * Setting this value to false will disable the creation, maintenance, and use of the USAP
69      * pool.  When the USAP pool is disabled the application lifecycle will be identical to
70      * previous versions of Android.
71      */
72     private boolean mUsapPoolEnabled = false;
73 
74     /**
75      * Listening socket that accepts new server connections.
76      */
77     private LocalServerSocket mZygoteSocket;
78 
79     /**
80      * The name of the unspecialized app process pool socket to use if the USAP pool is enabled.
81      */
82     private final LocalServerSocket mUsapPoolSocket;
83 
84     /**
85      * File descriptor used for communication between the signal handler and the ZygoteServer poll
86      * loop.
87      * */
88     private final FileDescriptor mUsapPoolEventFD;
89 
90     /**
91      * Whether or not mZygoteSocket's underlying FD should be closed directly.
92      * If mZygoteSocket is created with an existing FD, closing the socket does
93      * not close the FD and it must be closed explicitly. If the socket is created
94      * with a name instead, then closing the socket will close the underlying FD
95      * and it should not be double-closed.
96      */
97     private boolean mCloseSocketFd;
98 
99     /**
100      * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
101      */
102     private boolean mIsForkChild;
103 
104     /**
105      * The runtime-adjustable maximum USAP pool size.
106      */
107     private int mUsapPoolSizeMax = 0;
108 
109     /**
110      * The runtime-adjustable minimum USAP pool size.
111      */
112     private int mUsapPoolSizeMin = 0;
113 
114     /**
115      * The runtime-adjustable value used to determine when to re-fill the USAP pool.  The pool will
116      * be re-filled when (mUsapPoolMax - gUsapPoolCount) >= sUsapPoolRefillThreshold.
117      */
118     private int mUsapPoolRefillThreshold = 0;
119 
120     /**
121      * Number of milliseconds to delay before refilling the pool if it hasn't reached its
122      * minimum value.
123      */
124     private int mUsapPoolRefillDelayMs = -1;
125 
126     /**
127      * If and when we should refill the USAP pool.
128      */
129     private UsapPoolRefillAction mUsapPoolRefillAction;
130     private long mUsapPoolRefillTriggerTimestamp;
131 
132     private enum UsapPoolRefillAction {
133         DELAYED,
134         IMMEDIATE,
135         NONE
136     }
137 
ZygoteServer()138     ZygoteServer() {
139         mUsapPoolEventFD = null;
140         mZygoteSocket = null;
141         mUsapPoolSocket = null;
142 
143         mUsapPoolSupported = false;
144     }
145 
146     /**
147      * Initialize the Zygote server with the Zygote server socket, USAP pool server socket, and USAP
148      * pool event FD.
149      *
150      * @param isPrimaryZygote  If this is the primary Zygote or not.
151      */
ZygoteServer(boolean isPrimaryZygote)152     ZygoteServer(boolean isPrimaryZygote) {
153         mUsapPoolEventFD = Zygote.getUsapPoolEventFD();
154 
155         if (isPrimaryZygote) {
156             mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);
157             mUsapPoolSocket =
158                     Zygote.createManagedSocketFromInitSocket(
159                             Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);
160         } else {
161             mZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME);
162             mUsapPoolSocket =
163                     Zygote.createManagedSocketFromInitSocket(
164                             Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);
165         }
166 
167         mUsapPoolSupported = true;
168         fetchUsapPoolPolicyProps();
169     }
170 
setForkChild()171     void setForkChild() {
172         mIsForkChild = true;
173     }
174 
isUsapPoolEnabled()175     public boolean isUsapPoolEnabled() {
176         return mUsapPoolEnabled;
177     }
178 
179     /**
180      * Registers a server socket for zygote command connections. This opens the server socket
181      * at the specified name in the abstract socket namespace.
182      */
registerServerSocketAtAbstractName(String socketName)183     void registerServerSocketAtAbstractName(String socketName) {
184         if (mZygoteSocket == null) {
185             try {
186                 mZygoteSocket = new LocalServerSocket(socketName);
187                 mCloseSocketFd = false;
188             } catch (IOException ex) {
189                 throw new RuntimeException(
190                         "Error binding to abstract socket '" + socketName + "'", ex);
191             }
192         }
193     }
194 
195     /**
196      * Waits for and accepts a single command connection. Throws
197      * RuntimeException on failure.
198      */
acceptCommandPeer(String abiList)199     private ZygoteConnection acceptCommandPeer(String abiList) {
200         try {
201             return createNewConnection(mZygoteSocket.accept(), abiList);
202         } catch (IOException ex) {
203             throw new RuntimeException(
204                     "IOException during accept()", ex);
205         }
206     }
207 
createNewConnection(LocalSocket socket, String abiList)208     protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
209             throws IOException {
210         return new ZygoteConnection(socket, abiList);
211     }
212 
213     /**
214      * Close and clean up zygote sockets. Called on shutdown and on the
215      * child's exit path.
216      */
closeServerSocket()217     void closeServerSocket() {
218         try {
219             if (mZygoteSocket != null) {
220                 FileDescriptor fd = mZygoteSocket.getFileDescriptor();
221                 mZygoteSocket.close();
222                 if (fd != null && mCloseSocketFd) {
223                     Os.close(fd);
224                 }
225             }
226         } catch (IOException ex) {
227             Log.e(TAG, "Zygote:  error closing sockets", ex);
228         } catch (ErrnoException ex) {
229             Log.e(TAG, "Zygote:  error closing descriptor", ex);
230         }
231 
232         mZygoteSocket = null;
233     }
234 
235     /**
236      * Return the server socket's underlying file descriptor, so that
237      * ZygoteConnection can pass it to the native code for proper
238      * closure after a child process is forked off.
239      */
240 
getZygoteSocketFileDescriptor()241     FileDescriptor getZygoteSocketFileDescriptor() {
242         return mZygoteSocket.getFileDescriptor();
243     }
244 
fetchUsapPoolPolicyProps()245     private void fetchUsapPoolPolicyProps() {
246         if (mUsapPoolSupported) {
247             mUsapPoolSizeMax = Integer.min(
248                 ZygoteConfig.getInt(
249                     ZygoteConfig.USAP_POOL_SIZE_MAX,
250                     ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT),
251                 ZygoteConfig.USAP_POOL_SIZE_MAX_LIMIT);
252 
253             mUsapPoolSizeMin = Integer.max(
254                 ZygoteConfig.getInt(
255                     ZygoteConfig.USAP_POOL_SIZE_MIN,
256                     ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT),
257                 ZygoteConfig.USAP_POOL_SIZE_MIN_LIMIT);
258 
259             mUsapPoolRefillThreshold = Integer.min(
260                 ZygoteConfig.getInt(
261                     ZygoteConfig.USAP_POOL_REFILL_THRESHOLD,
262                     ZygoteConfig.USAP_POOL_REFILL_THRESHOLD_DEFAULT),
263                 mUsapPoolSizeMax);
264 
265             mUsapPoolRefillDelayMs = ZygoteConfig.getInt(
266                 ZygoteConfig.USAP_POOL_REFILL_DELAY_MS,
267                 ZygoteConfig.USAP_POOL_REFILL_DELAY_MS_DEFAULT);
268 
269             // Validity check
270             if (mUsapPoolSizeMin >= mUsapPoolSizeMax) {
271                 Log.w(TAG, "The max size of the USAP pool must be greater than the minimum size."
272                         + "  Restoring default values.");
273 
274                 mUsapPoolSizeMax = ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT;
275                 mUsapPoolSizeMin = ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT;
276                 mUsapPoolRefillThreshold = mUsapPoolSizeMax / 2;
277             }
278         }
279     }
280 
281     private boolean mIsFirstPropertyCheck = true;
282     private long mLastPropCheckTimestamp = 0;
283 
fetchUsapPoolPolicyPropsWithMinInterval()284     private void fetchUsapPoolPolicyPropsWithMinInterval() {
285         final long currentTimestamp = SystemClock.elapsedRealtime();
286 
287         if (mIsFirstPropertyCheck
288                 || (currentTimestamp - mLastPropCheckTimestamp >= Zygote.PROPERTY_CHECK_INTERVAL)) {
289             mIsFirstPropertyCheck = false;
290             mLastPropCheckTimestamp = currentTimestamp;
291             fetchUsapPoolPolicyProps();
292         }
293     }
294 
fetchUsapPoolPolicyPropsIfUnfetched()295     private void fetchUsapPoolPolicyPropsIfUnfetched() {
296         if (mIsFirstPropertyCheck) {
297             mIsFirstPropertyCheck = false;
298             fetchUsapPoolPolicyProps();
299         }
300     }
301 
302     /**
303      * Refill the USAP Pool to the appropriate level, determined by whether this is a priority
304      * refill event or not.
305      *
306      * @param sessionSocketRawFDs  Anonymous session sockets that are currently open
307      * @return In the Zygote process this function will always return null; in unspecialized app
308      *         processes this function will return a Runnable object representing the new
309      *         application that is passed up from childMain (the usap's main wait loop).
310      */
311 
fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill)312     Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) {
313         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Zygote:FillUsapPool");
314 
315         // Ensure that the pool properties have been fetched.
316         fetchUsapPoolPolicyPropsIfUnfetched();
317 
318         int usapPoolCount = Zygote.getUsapPoolCount();
319         int numUsapsToSpawn;
320 
321         if (isPriorityRefill) {
322             // Refill to min
323             numUsapsToSpawn = mUsapPoolSizeMin - usapPoolCount;
324 
325             Log.i("zygote",
326                     "Priority USAP Pool refill. New USAPs: " + numUsapsToSpawn);
327         } else {
328             // Refill up to max
329             numUsapsToSpawn = mUsapPoolSizeMax - usapPoolCount;
330 
331             Log.i("zygote",
332                     "Delayed USAP Pool refill. New USAPs: " + numUsapsToSpawn);
333         }
334 
335         // Disable some VM functionality and reset some system values
336         // before forking.
337         ZygoteHooks.preFork();
338 
339         while (--numUsapsToSpawn >= 0) {
340             Runnable caller =
341                     Zygote.forkUsap(mUsapPoolSocket, sessionSocketRawFDs, isPriorityRefill);
342 
343             if (caller != null) {
344                 return caller;
345             }
346         }
347 
348         // Re-enable runtime services for the Zygote.  Services for unspecialized app process
349         // are re-enabled in specializeAppProcess.
350         ZygoteHooks.postForkCommon();
351 
352         resetUsapRefillState();
353 
354         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
355 
356         return null;
357     }
358 
359     /**
360      * Empty or fill the USAP pool as dictated by the current and new USAP pool statuses.
361      */
setUsapPoolStatus(boolean newStatus, LocalSocket sessionSocket)362     Runnable setUsapPoolStatus(boolean newStatus, LocalSocket sessionSocket) {
363         if (!mUsapPoolSupported) {
364             Log.w(TAG,
365                     "Attempting to enable a USAP pool for a Zygote that doesn't support it.");
366             return null;
367         } else if (mUsapPoolEnabled == newStatus) {
368             return null;
369         }
370 
371         Log.i(TAG, "USAP Pool status change: " + (newStatus ? "ENABLED" : "DISABLED"));
372 
373         mUsapPoolEnabled = newStatus;
374 
375         if (newStatus) {
376             return fillUsapPool(new int[]{ sessionSocket.getFileDescriptor().getInt$() }, false);
377         } else {
378             Zygote.emptyUsapPool();
379             return null;
380         }
381     }
382 
resetUsapRefillState()383     private void resetUsapRefillState() {
384         mUsapPoolRefillAction = UsapPoolRefillAction.NONE;
385         mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
386     }
387 
388     /**
389      * Runs the zygote process's select loop. Accepts new connections as
390      * they happen, and reads commands from connections one spawn-request's
391      * worth at a time.
392      * @param abiList list of ABIs supported by this zygote.
393      */
runSelectLoop(String abiList)394     Runnable runSelectLoop(String abiList) {
395         ArrayList<FileDescriptor> socketFDs = new ArrayList<>();
396         ArrayList<ZygoteConnection> peers = new ArrayList<>();
397 
398         socketFDs.add(mZygoteSocket.getFileDescriptor());
399         peers.add(null);
400 
401         mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
402 
403         while (true) {
404             fetchUsapPoolPolicyPropsWithMinInterval();
405             mUsapPoolRefillAction = UsapPoolRefillAction.NONE;
406 
407             int[] usapPipeFDs = null;
408             StructPollfd[] pollFDs;
409 
410             // Allocate enough space for the poll structs, taking into account
411             // the state of the USAP pool for this Zygote (could be a
412             // regular Zygote, a WebView Zygote, or an AppZygote).
413             if (mUsapPoolEnabled) {
414                 usapPipeFDs = Zygote.getUsapPipeFDs();
415                 pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length];
416             } else {
417                 pollFDs = new StructPollfd[socketFDs.size()];
418             }
419 
420             /*
421              * For reasons of correctness the USAP pool pipe and event FDs
422              * must be processed before the session and server sockets.  This
423              * is to ensure that the USAP pool accounting information is
424              * accurate when handling other requests like API deny list
425              * exemptions.
426              */
427 
428             int pollIndex = 0;
429             for (FileDescriptor socketFD : socketFDs) {
430                 pollFDs[pollIndex] = new StructPollfd();
431                 pollFDs[pollIndex].fd = socketFD;
432                 pollFDs[pollIndex].events = (short) POLLIN;
433                 ++pollIndex;
434             }
435 
436             final int usapPoolEventFDIndex = pollIndex;
437 
438             if (mUsapPoolEnabled) {
439                 pollFDs[pollIndex] = new StructPollfd();
440                 pollFDs[pollIndex].fd = mUsapPoolEventFD;
441                 pollFDs[pollIndex].events = (short) POLLIN;
442                 ++pollIndex;
443 
444                 // The usapPipeFDs array will always be filled in if the USAP Pool is enabled.
445                 assert usapPipeFDs != null;
446                 for (int usapPipeFD : usapPipeFDs) {
447                     FileDescriptor managedFd = new FileDescriptor();
448                     managedFd.setInt$(usapPipeFD);
449 
450                     pollFDs[pollIndex] = new StructPollfd();
451                     pollFDs[pollIndex].fd = managedFd;
452                     pollFDs[pollIndex].events = (short) POLLIN;
453                     ++pollIndex;
454                 }
455             }
456 
457             int pollTimeoutMs;
458 
459             if (mUsapPoolRefillTriggerTimestamp == INVALID_TIMESTAMP) {
460                 pollTimeoutMs = -1;
461             } else {
462                 long elapsedTimeMs = System.currentTimeMillis() - mUsapPoolRefillTriggerTimestamp;
463 
464                 if (elapsedTimeMs >= mUsapPoolRefillDelayMs) {
465                     // The refill delay has elapsed during the period between poll invocations.
466                     // We will now check for any currently ready file descriptors before refilling
467                     // the USAP pool.
468                     pollTimeoutMs = 0;
469                     mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
470                     mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
471 
472                 } else if (elapsedTimeMs <= 0) {
473                     // This can occur if the clock used by currentTimeMillis is reset, which is
474                     // possible because it is not guaranteed to be monotonic.  Because we can't tell
475                     // how far back the clock was set the best way to recover is to simply re-start
476                     // the respawn delay countdown.
477                     pollTimeoutMs = mUsapPoolRefillDelayMs;
478 
479                 } else {
480                     pollTimeoutMs = (int) (mUsapPoolRefillDelayMs - elapsedTimeMs);
481                 }
482             }
483 
484             int pollReturnValue;
485             try {
486                 pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);
487             } catch (ErrnoException ex) {
488                 throw new RuntimeException("poll failed", ex);
489             }
490 
491             if (pollReturnValue == 0) {
492                 // The poll returned zero results either when the timeout value has been exceeded
493                 // or when a non-blocking poll is issued and no FDs are ready.  In either case it
494                 // is time to refill the pool.  This will result in a duplicate assignment when
495                 // the non-blocking poll returns zero results, but it avoids an additional
496                 // conditional in the else branch.
497                 mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
498                 mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
499 
500             } else {
501                 boolean usapPoolFDRead = false;
502 
503                 while (--pollIndex >= 0) {
504                     if ((pollFDs[pollIndex].revents & POLLIN) == 0) {
505                         continue;
506                     }
507 
508                     if (pollIndex == 0) {
509                         // Zygote server socket
510                         ZygoteConnection newPeer = acceptCommandPeer(abiList);
511                         peers.add(newPeer);
512                         socketFDs.add(newPeer.getFileDescriptor());
513                     } else if (pollIndex < usapPoolEventFDIndex) {
514                         // Session socket accepted from the Zygote server socket
515 
516                         try {
517                             ZygoteConnection connection = peers.get(pollIndex);
518                             boolean multipleForksOK = !isUsapPoolEnabled()
519                                     && ZygoteHooks.isIndefiniteThreadSuspensionSafe();
520                             final Runnable command =
521                                     connection.processCommand(this, multipleForksOK);
522 
523                             // TODO (chriswailes): Is this extra check necessary?
524                             if (mIsForkChild) {
525                                 // We're in the child. We should always have a command to run at
526                                 // this stage if processCommand hasn't called "exec".
527                                 if (command == null) {
528                                     throw new IllegalStateException("command == null");
529                                 }
530 
531                                 return command;
532                             } else {
533                                 // We're in the server - we should never have any commands to run.
534                                 if (command != null) {
535                                     throw new IllegalStateException("command != null");
536                                 }
537 
538                                 // We don't know whether the remote side of the socket was closed or
539                                 // not until we attempt to read from it from processCommand. This
540                                 // shows up as a regular POLLIN event in our regular processing
541                                 // loop.
542                                 if (connection.isClosedByPeer()) {
543                                     connection.closeSocket();
544                                     peers.remove(pollIndex);
545                                     socketFDs.remove(pollIndex);
546                                 }
547                             }
548                         } catch (Exception e) {
549                             if (!mIsForkChild) {
550                                 // We're in the server so any exception here is one that has taken
551                                 // place pre-fork while processing commands or reading / writing
552                                 // from the control socket. Make a loud noise about any such
553                                 // exceptions so that we know exactly what failed and why.
554 
555                                 Slog.e(TAG, "Exception executing zygote command: ", e);
556 
557                                 // Make sure the socket is closed so that the other end knows
558                                 // immediately that something has gone wrong and doesn't time out
559                                 // waiting for a response.
560                                 ZygoteConnection conn = peers.remove(pollIndex);
561                                 conn.closeSocket();
562 
563                                 socketFDs.remove(pollIndex);
564                             } else {
565                                 // We're in the child so any exception caught here has happened post
566                                 // fork and before we execute ActivityThread.main (or any other
567                                 // main() method). Log the details of the exception and bring down
568                                 // the process.
569                                 Log.e(TAG, "Caught post-fork exception in child process.", e);
570                                 throw e;
571                             }
572                         } finally {
573                             // Reset the child flag, in the event that the child process is a child-
574                             // zygote. The flag will not be consulted this loop pass after the
575                             // Runnable is returned.
576                             mIsForkChild = false;
577                         }
578 
579                     } else {
580                         // Either the USAP pool event FD or a USAP reporting pipe.
581 
582                         // If this is the event FD the payload will be the number of USAPs removed.
583                         // If this is a reporting pipe FD the payload will be the PID of the USAP
584                         // that was just specialized.  The `continue` statements below ensure that
585                         // the messagePayload will always be valid if we complete the try block
586                         // without an exception.
587                         long messagePayload;
588 
589                         try {
590                             byte[] buffer = new byte[Zygote.USAP_MANAGEMENT_MESSAGE_BYTES];
591                             int readBytes =
592                                     Os.read(pollFDs[pollIndex].fd, buffer, 0, buffer.length);
593 
594                             if (readBytes == Zygote.USAP_MANAGEMENT_MESSAGE_BYTES) {
595                                 DataInputStream inputStream =
596                                         new DataInputStream(new ByteArrayInputStream(buffer));
597 
598                                 messagePayload = inputStream.readLong();
599                             } else {
600                                 Log.e(TAG, "Incomplete read from USAP management FD of size "
601                                         + readBytes);
602                                 continue;
603                             }
604                         } catch (Exception ex) {
605                             if (pollIndex == usapPoolEventFDIndex) {
606                                 Log.e(TAG, "Failed to read from USAP pool event FD: "
607                                         + ex.getMessage());
608                             } else {
609                                 Log.e(TAG, "Failed to read from USAP reporting pipe: "
610                                         + ex.getMessage());
611                             }
612 
613                             continue;
614                         }
615 
616                         if (pollIndex > usapPoolEventFDIndex) {
617                             Zygote.removeUsapTableEntry((int) messagePayload);
618                         }
619 
620                         usapPoolFDRead = true;
621                     }
622                 }
623 
624                 if (usapPoolFDRead) {
625                     int usapPoolCount = Zygote.getUsapPoolCount();
626 
627                     if (usapPoolCount < mUsapPoolSizeMin) {
628                         // Immediate refill
629                         mUsapPoolRefillAction = UsapPoolRefillAction.IMMEDIATE;
630                     } else if (mUsapPoolSizeMax - usapPoolCount >= mUsapPoolRefillThreshold) {
631                         // Delayed refill
632                         mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis();
633                     }
634                 }
635             }
636 
637             if (mUsapPoolRefillAction != UsapPoolRefillAction.NONE) {
638                 int[] sessionSocketRawFDs =
639                         socketFDs.subList(1, socketFDs.size())
640                                 .stream()
641                                 .mapToInt(FileDescriptor::getInt$)
642                                 .toArray();
643 
644                 final boolean isPriorityRefill =
645                         mUsapPoolRefillAction == UsapPoolRefillAction.IMMEDIATE;
646 
647                 final Runnable command =
648                         fillUsapPool(sessionSocketRawFDs, isPriorityRefill);
649 
650                 if (command != null) {
651                     return command;
652                 } else if (isPriorityRefill) {
653                     // Schedule a delayed refill to finish refilling the pool.
654                     mUsapPoolRefillTriggerTimestamp = System.currentTimeMillis();
655                 }
656             }
657         }
658     }
659 }
660