blob: 7538843f9b664ae4c7b1c48a67756193b3e1a0cf [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.task;
import static java.util.concurrent.TimeUnit.SECONDS;
import org.chromium.base.BuildConfig;
import org.chromium.base.VisibleForTesting;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class ChromeThreadPoolExecutor extends ThreadPoolExecutor {
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// Core pool is still used despite allowCoreThreadTimeOut(true) being called - while the core
// pool can still timeout, the thread pool will still start up threads more aggressively while
// under the CORE_POOL_SIZE.
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "CrAsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new ArrayBlockingQueue<Runnable>(128);
// May have to be lowered if we are not capturing any Runnable sources.
private static final int RUNNABLE_WARNING_COUNT = 32;
ChromeThreadPoolExecutor() {
this(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, SECONDS, sPoolWorkQueue,
sThreadFactory);
}
@VisibleForTesting
ChromeThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
allowCoreThreadTimeOut(true);
}
@SuppressWarnings("NoAndroidAsyncTaskCheck")
private static String getClassName(Runnable runnable) {
Class blamedClass = runnable.getClass();
try {
if (blamedClass == AsyncTask.NamedFutureTask.class) {
blamedClass = ((AsyncTask.NamedFutureTask) runnable).getBlamedClass();
} else if (blamedClass.getEnclosingClass() == android.os.AsyncTask.class) {
// This gets the AsyncTask that produced the runnable.
Field field = blamedClass.getDeclaredField("this$0");
field.setAccessible(true);
blamedClass = field.get(runnable).getClass();
}
} catch (NoSuchFieldException e) {
if (BuildConfig.DCHECK_IS_ON) {
throw new RuntimeException(e);
}
} catch (IllegalAccessException e) {
if (BuildConfig.DCHECK_IS_ON) {
throw new RuntimeException(e);
}
}
return blamedClass.getName();
}
private Map<String, Integer> getNumberOfClassNameOccurrencesInQueue() {
Map<String, Integer> counts = new HashMap<>();
Runnable[] copiedQueue = getQueue().toArray(new Runnable[0]);
for (Runnable runnable : copiedQueue) {
String className = getClassName(runnable);
int count = counts.containsKey(className) ? counts.get(className) : 0;
counts.put(className, count + 1);
}
return counts;
}
private String findClassNamesWithTooManyRunnables(Map<String, Integer> counts) {
// We only show the classes over RUNNABLE_WARNING_COUNT appearances so that these
// crashes group up together in the reporting dashboards. If we were to print all
// the Runnables or their counts, this would fragment the reporting, with one for
// each unique set of Runnables/counts.
StringBuilder classesWithTooManyRunnables = new StringBuilder();
for (Map.Entry<String, Integer> entry : counts.entrySet()) {
if (entry.getValue() > RUNNABLE_WARNING_COUNT) {
classesWithTooManyRunnables.append(entry.getKey()).append(' ');
}
}
if (classesWithTooManyRunnables.length() == 0) {
return "NO CLASSES FOUND";
}
return classesWithTooManyRunnables.toString();
}
@Override
public void execute(Runnable command) {
try {
super.execute(command);
} catch (RejectedExecutionException e) {
Map<String, Integer> counts = getNumberOfClassNameOccurrencesInQueue();
throw new RejectedExecutionException(
"Prominent classes in AsyncTask: " + findClassNamesWithTooManyRunnables(counts),
e);
}
}
}