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