• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.task;
6 
7 import static java.util.concurrent.TimeUnit.SECONDS;
8 
9 import androidx.annotation.VisibleForTesting;
10 
11 import org.chromium.build.BuildConfig;
12 
13 import java.lang.reflect.Field;
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.concurrent.ArrayBlockingQueue;
17 import java.util.concurrent.BlockingQueue;
18 import java.util.concurrent.RejectedExecutionException;
19 import java.util.concurrent.ThreadFactory;
20 import java.util.concurrent.ThreadPoolExecutor;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.atomic.AtomicInteger;
23 
24 class ChromeThreadPoolExecutor extends ThreadPoolExecutor {
25     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
26 
27     // Core pool is still used despite allowCoreThreadTimeOut(true) being called - while the core
28     // pool can still timeout, the thread pool will still start up threads more aggressively while
29     // under the CORE_POOL_SIZE.
30     private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
31     private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
32     private static final int KEEP_ALIVE_SECONDS = 30;
33 
34     private static final ThreadFactory sThreadFactory =
35             new ThreadFactory() {
36                 private final AtomicInteger mCount = new AtomicInteger(1);
37 
38                 @Override
39                 public Thread newThread(Runnable r) {
40                     return new Thread(r, "CrAsyncTask #" + mCount.getAndIncrement());
41                 }
42             };
43 
44     private static final BlockingQueue<Runnable> sPoolWorkQueue =
45             new ArrayBlockingQueue<Runnable>(128);
46 
47     // May have to be lowered if we are not capturing any Runnable sources.
48     private static final int RUNNABLE_WARNING_COUNT = 32;
49 
ChromeThreadPoolExecutor()50     ChromeThreadPoolExecutor() {
51         this(
52                 CORE_POOL_SIZE,
53                 MAXIMUM_POOL_SIZE,
54                 KEEP_ALIVE_SECONDS,
55                 SECONDS,
56                 sPoolWorkQueue,
57                 sThreadFactory);
58     }
59 
60     @VisibleForTesting
ChromeThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)61     ChromeThreadPoolExecutor(
62             int corePoolSize,
63             int maximumPoolSize,
64             long keepAliveTime,
65             TimeUnit unit,
66             BlockingQueue<Runnable> workQueue,
67             ThreadFactory threadFactory) {
68         super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
69         allowCoreThreadTimeOut(true);
70     }
71 
72     @SuppressWarnings("NoAndroidAsyncTaskCheck")
getClassName(Runnable runnable)73     private static String getClassName(Runnable runnable) {
74         Class blamedClass = runnable.getClass();
75         try {
76             if (blamedClass == AsyncTask.NamedFutureTask.class) {
77                 blamedClass = ((AsyncTask.NamedFutureTask) runnable).getBlamedClass();
78             } else if (blamedClass.getEnclosingClass() == android.os.AsyncTask.class) {
79                 // This gets the AsyncTask that produced the runnable.
80                 Field field = blamedClass.getDeclaredField("this$0");
81                 field.setAccessible(true);
82                 blamedClass = field.get(runnable).getClass();
83             }
84         } catch (NoSuchFieldException e) {
85             if (BuildConfig.ENABLE_ASSERTS) {
86                 throw new RuntimeException(e);
87             }
88         } catch (IllegalAccessException e) {
89             if (BuildConfig.ENABLE_ASSERTS) {
90                 throw new RuntimeException(e);
91             }
92         }
93         return blamedClass.getName();
94     }
95 
getNumberOfClassNameOccurrencesInQueue()96     private Map<String, Integer> getNumberOfClassNameOccurrencesInQueue() {
97         Map<String, Integer> counts = new HashMap<>();
98         Runnable[] copiedQueue = getQueue().toArray(new Runnable[0]);
99         for (Runnable runnable : copiedQueue) {
100             String className = getClassName(runnable);
101             int count = counts.containsKey(className) ? counts.get(className) : 0;
102             counts.put(className, count + 1);
103         }
104         return counts;
105     }
106 
findClassNamesWithTooManyRunnables(Map<String, Integer> counts)107     private String findClassNamesWithTooManyRunnables(Map<String, Integer> counts) {
108         // We only show the classes over RUNNABLE_WARNING_COUNT appearances so that these
109         // crashes group up together in the reporting dashboards. If we were to print all
110         // the Runnables or their counts, this would fragment the reporting, with one for
111         // each unique set of Runnables/counts.
112         StringBuilder classesWithTooManyRunnables = new StringBuilder();
113         for (Map.Entry<String, Integer> entry : counts.entrySet()) {
114             if (entry.getValue() > RUNNABLE_WARNING_COUNT) {
115                 classesWithTooManyRunnables.append(entry.getKey()).append(' ');
116             }
117         }
118         if (classesWithTooManyRunnables.length() == 0) {
119             return "NO CLASSES FOUND";
120         }
121         return classesWithTooManyRunnables.toString();
122     }
123 
124     @Override
execute(Runnable r)125     public void execute(Runnable r) {
126         try {
127             super.execute(r);
128         } catch (RejectedExecutionException e) {
129             Map<String, Integer> counts = getNumberOfClassNameOccurrencesInQueue();
130 
131             throw new RejectedExecutionException(
132                     "Prominent classes in AsyncTask: " + findClassNamesWithTooManyRunnables(counts),
133                     e);
134         }
135     }
136 }
137