/* * Copyright 2015-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.cli; import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; import com.facebook.buck.log.CommandThreadFactory; import com.facebook.buck.util.concurrent.ConcurrencyLimit; import com.facebook.buck.util.concurrent.ListeningMultiSemaphore; import com.facebook.buck.util.concurrent.MostExecutors; import com.facebook.buck.util.concurrent.ResourceAmounts; import com.facebook.buck.util.concurrent.WeightedListeningExecutorService; import com.google.common.base.Joiner; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * Encapsulates a group of threads which operate a {@link ListeningExecutorService}, providing an * {@link AutoCloseable} interface which waits for and kills the threads on close. */ @SuppressWarnings("PMD.AvoidThreadGroup") public class CommandThreadManager implements AutoCloseable { // Shutdown timeout should be longer than the maximum runtime of a single step as some // steps ignore interruption. The longest ever recorded step execution time as of // 2014-07-08 was ~6 minutes, so a timeout of 10 minutes should be sufficient. private static final long DEFAULT_SHUTDOWN_TIMEOUT = 30; private static final TimeUnit DEFAULT_SHUTDOWN_TIMEOUT_UNIT = TimeUnit.MINUTES; // Use a thread group purely as a debugging aid to help enumerate the threads we should // print an error message for. private final ThreadGroup threadGroup; private final WeightedListeningExecutorService executor; // How long to wait for the executor service to shutdown. private final long shutdownTimeout; private final TimeUnit shutdownTimeoutUnit; public CommandThreadManager( String name, ListeningMultiSemaphore semaphore, ResourceAmounts defaultAmounts, int managedThreadCount, long shutdownTimeout, TimeUnit shutdownTimeoutUnit) { this.threadGroup = new ThreadGroup(name); // TODO(cjhopman): This should probably take a Function<ThreadGroup, ListeningExecutorService> // so that all that this class is really in charge of is properly shutting it down and providing // useful information when that fails. this.executor = new WeightedListeningExecutorService( semaphore, defaultAmounts, listeningDecorator( MostExecutors.newMultiThreadExecutor( new ThreadFactoryBuilder() .setNameFormat(name + "-%d") .setThreadFactory(new CommandThreadFactory(r -> new Thread(threadGroup, r))) .build(), managedThreadCount))); this.shutdownTimeout = shutdownTimeout; this.shutdownTimeoutUnit = shutdownTimeoutUnit; } public CommandThreadManager( String name, ListeningMultiSemaphore semaphore, ResourceAmounts defaultAmounts, int managedThreadCount) { this( name, semaphore, defaultAmounts, managedThreadCount, DEFAULT_SHUTDOWN_TIMEOUT, DEFAULT_SHUTDOWN_TIMEOUT_UNIT); } public CommandThreadManager( String name, ConcurrencyLimit concurrencyLimit, long shutdownTimeout, TimeUnit shutdownTimeoutUnit) { this( name, new ListeningMultiSemaphore( concurrencyLimit.maximumAmounts, concurrencyLimit.resourceAllocationFairness), concurrencyLimit.defaultAmounts, concurrencyLimit.managedThreadCount, shutdownTimeout, shutdownTimeoutUnit); } public CommandThreadManager(String name, ConcurrencyLimit concurrencyLimit) { this(name, concurrencyLimit, DEFAULT_SHUTDOWN_TIMEOUT, DEFAULT_SHUTDOWN_TIMEOUT_UNIT); } public WeightedListeningExecutorService getExecutor() { return executor; } @Override public void close() throws InterruptedException { boolean shutdown = MostExecutors.shutdown(executor, shutdownTimeout, shutdownTimeoutUnit); // If the shutdown failed, print the stacks for all the blocked threads. if (!shutdown) { List<String> parts = new ArrayList<>(); parts.add(String.format("Shutdown timed out for thread pool %s", threadGroup.getName())); Thread[] threads = new Thread[threadGroup.activeCount()]; threadGroup.enumerate(threads); for (Thread thread : threads) { if (thread.getState() != Thread.State.TERMINATED) { parts.add(" Thread " + thread.getName() + ":"); for (StackTraceElement element : thread.getStackTrace()) { parts.add(String.format(" %s", element.toString())); } } } throw new RuntimeException(Joiner.on("\n").join(parts)); } } }