• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.adservices.concurrency;
18 
19 import android.annotation.NonNull;
20 import android.annotation.SuppressLint;
21 
22 import com.google.common.util.concurrent.ListeningExecutorService;
23 import com.google.common.util.concurrent.MoreExecutors;
24 
25 import java.util.Locale;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.LinkedBlockingQueue;
28 import java.util.concurrent.ScheduledThreadPoolExecutor;
29 import java.util.concurrent.ThreadFactory;
30 import java.util.concurrent.ThreadPoolExecutor;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.atomic.AtomicLong;
33 
34 /**
35  * All executors of the PP API module.
36  *
37  * @hide
38  */
39 // TODO(b/224987182): set appropriate parameters (priority, size, etc..) for the shared thread pools
40 // after doing detailed analysis. Ideally the parameters should be backed by PH flags.
41 public final class AdServicesExecutors {
42     // We set the minimal number of threads for background executor to 4 and lightweight & scheduled
43     //  executors to 2 since Runtime.getRuntime().availableProcessors() may return 1 or 2 for
44     //  low-end devices. This may cause deadlock for starvation in those low-end devices.
45     private static final int MIN_BACKGROUND_EXECUTOR_THREADS = 4;
46     private static final int MIN_LIGHTWEIGHT_EXECUTOR_THREADS = 2;
47     private static final int MAX_SCHEDULED_EXECUTOR_THREADS = 4;
48 
49     private static final String LIGHTWEIGHT_NAME = "lightweight";
50     private static final String BACKGROUND_NAME = "background";
51     private static final String SCHEDULED_NAME = "scheduled";
52     private static final String BLOCKING_NAME = "blocking";
53 
getFactory(final String threadPrefix)54     private static ThreadFactory getFactory(final String threadPrefix) {
55         return new ThreadFactory() {
56             private final AtomicLong mThreadCount = new AtomicLong(0L);
57 
58             @SuppressLint("DefaultLocale")
59             @Override
60             public Thread newThread(Runnable runnable) {
61                 Thread thread = Executors.defaultThreadFactory().newThread(runnable);
62                 thread.setName(
63                         String.format(
64                                 Locale.US, "%s-%d", threadPrefix, mThreadCount.incrementAndGet()));
65                 return thread;
66             }
67         };
68     }
69 
70     private static final ListeningExecutorService sLightWeightExecutor =
71             // Always use at least two threads, so that clients can't depend on light-weight
72             // executor tasks executing sequentially
73             MoreExecutors.listeningDecorator(
74                     new ThreadPoolExecutor(
75                             /* corePoolSize= */ Math.max(
76                                     MIN_LIGHTWEIGHT_EXECUTOR_THREADS,
77                                     Runtime.getRuntime().availableProcessors() - 2),
78                             /* maximumPoolSize */
79                             Math.max(
80                                     MIN_LIGHTWEIGHT_EXECUTOR_THREADS,
81                                     Runtime.getRuntime().availableProcessors() - 2),
82                             /* keepAliveTime= */ 60L,
83                             TimeUnit.SECONDS,
84                             new LinkedBlockingQueue<>(),
85                             getFactory(LIGHTWEIGHT_NAME)));
86 
87     /**
88      * Functions that don't do direct I/O and that are fast (under ten milliseconds or thereabouts)
89      * should run on this Executor.
90      *
91      * <p>Most async code in an app should be written to run on this Executor.
92      */
93     @NonNull
getLightWeightExecutor()94     public static ListeningExecutorService getLightWeightExecutor() {
95         return sLightWeightExecutor;
96     }
97 
98     private static final ListeningExecutorService sBackgroundExecutor =
99             MoreExecutors.listeningDecorator(
100                     new ThreadPoolExecutor(
101                             /* corePoolSize= */ Math.max(
102                                     MIN_BACKGROUND_EXECUTOR_THREADS,
103                                     Runtime.getRuntime().availableProcessors()),
104                             /* maximumPoolSize */ Math.max(
105                                     MIN_BACKGROUND_EXECUTOR_THREADS,
106                                     Runtime.getRuntime().availableProcessors()),
107                             /* keepAliveTime= */ 60L,
108                             TimeUnit.SECONDS,
109                             new LinkedBlockingQueue<>(),
110                             getFactory(BACKGROUND_NAME)));
111 
112     /**
113      * Functions that directly execute disk I/O, or that are CPU bound and long-running (over ten
114      * milliseconds or thereabouts) should run on this Executor.
115      *
116      * <p>Examples include stepping through a database Cursor, or decoding an image into a Bitmap.
117      *
118      * <p>Functions that block on network I/O must run on BlockingExecutor.
119      */
120     @NonNull
getBackgroundExecutor()121     public static ListeningExecutorService getBackgroundExecutor() {
122         return sBackgroundExecutor;
123     }
124 
125     private static final ScheduledThreadPoolExecutor sScheduler =
126             new ScheduledThreadPoolExecutor(
127                     /* corePoolSize= */ Math.min(
128                             MAX_SCHEDULED_EXECUTOR_THREADS,
129                             Runtime.getRuntime().availableProcessors()),
130                     getFactory(SCHEDULED_NAME));
131 
132     /**
133      * Functions that require to be run with a delay, or have timed executions should run on this
134      * Executor.
135      *
136      * <p>Example includes having timeouts on Futures.
137      *
138      * @return
139      */
140     @NonNull
getScheduler()141     public static ScheduledThreadPoolExecutor getScheduler() {
142         return sScheduler;
143     }
144 
145     private static final ListeningExecutorService sBlockingExecutor =
146             MoreExecutors.listeningDecorator(
147                     Executors.newCachedThreadPool(getFactory(BLOCKING_NAME)));
148 
149     /**
150      * Functions that directly execute network I/O, or that block their thread awaiting the progress
151      * of at least one other thread, must run on BlockingExecutor.
152      *
153      * <p>BlockingExecutor will launch as many threads as there are tasks available to run
154      * concurrently, stopping and freeing them when the concurrent task count drops again. This
155      * unbounded number of threads negatively impacts performance:
156      *
157      * <p>Extra threads add execution overhead and increase execution latency. Each thread consumes
158      * significant memory for thread-local state and stack, and may increase the total amount of
159      * space used by objects on the heap. Each additional BlockingExecutor thread reduces the time
160      * available to the fixed-size LightweightExecutor and BackgroundExecutor. While
161      * BlockingExecutor's threads have a lower priority to decrease this impact, the extra threads
162      * can still compete for resources. Always prefer to refactor a class or API to avoid blocking
163      * before falling back to using the blocking Executor.
164      */
165     @NonNull
getBlockingExecutor()166     public static ListeningExecutorService getBlockingExecutor() {
167         return sBlockingExecutor;
168     }
169 
AdServicesExecutors()170     private AdServicesExecutors() {}
171 }
172