/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.kafka.connect.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * <p> * Thread class with support for triggering graceful and forcible shutdown. In graceful shutdown, * a flag is set, which the thread should detect and try to exit gracefully from. In forcible * shutdown, the thread is interrupted. These can be combined to give a thread a chance to exit * gracefully, but then force it to exit if it takes too long. * </p> * <p> * Implementations should override the {@link #execute} method and check {@link #getRunning} to * determine whether they should try to gracefully exit. * </p> */ public abstract class ShutdownableThread extends Thread { private static final Logger log = LoggerFactory.getLogger(ShutdownableThread.class); private AtomicBoolean isRunning = new AtomicBoolean(true); private CountDownLatch shutdownLatch = new CountDownLatch(1); /** * An UncaughtExceptionHandler to register on every instance of this class. This is useful for * testing, where AssertionExceptions in the thread may not cause the test to fail. Since one * instance is used for all threads, it must be thread-safe. */ volatile public static UncaughtExceptionHandler funcaughtExceptionHandler = null; public ShutdownableThread(String name) { // The default is daemon=true so that these threads will not prevent shutdown. We use this // default because threads that are running user code that may not clean up properly, even // when we attempt to forcibly shut them down. this(name, true); } public ShutdownableThread(String name, boolean daemon) { super(name); this.setDaemon(daemon); if (funcaughtExceptionHandler != null) this.setUncaughtExceptionHandler(funcaughtExceptionHandler); } /** * Implementations should override this method with the main body for the thread. */ public abstract void execute(); /** * Returns true if the thread hasn't exited yet and none of the shutdown methods have been * invoked */ public boolean getRunning() { return isRunning.get(); } @Override public void run() { try { execute(); } catch (Error | RuntimeException e) { log.error("Thread {} exiting with uncaught exception: ", getName(), e); throw e; } finally { shutdownLatch.countDown(); } } /** * Shutdown the thread, first trying to shut down gracefully using the specified timeout, then * forcibly interrupting the thread. * @param gracefulTimeout the maximum time to wait for a graceful exit * @param unit the time unit of the timeout argument */ public void shutdown(long gracefulTimeout, TimeUnit unit) throws InterruptedException { boolean success = gracefulShutdown(gracefulTimeout, unit); if (!success) forceShutdown(); } /** * Attempt graceful shutdown * @param timeout the maximum time to wait * @param unit the time unit of the timeout argument * @return true if successful, false if the timeout elapsed */ public boolean gracefulShutdown(long timeout, TimeUnit unit) throws InterruptedException { startGracefulShutdown(); return awaitShutdown(timeout, unit); } /** * Start shutting down this thread gracefully, but do not block waiting for it to exit. */ public void startGracefulShutdown() { log.info("Starting graceful shutdown of thread {}", getName()); isRunning.set(false); } /** * Awaits shutdown of this thread, waiting up to the timeout. * @param timeout the maximum time to wait * @param unit the time unit of the timeout argument * @return true if successful, false if the timeout elapsed * @throws InterruptedException */ public boolean awaitShutdown(long timeout, TimeUnit unit) throws InterruptedException { return shutdownLatch.await(timeout, unit); } /** * Immediately tries to force the thread to shut down by interrupting it. This does not try to * wait for the thread to truly exit because forcible shutdown is not always possible. By * default, threads are marked as daemon threads so they will not prevent the process from * exiting. */ public void forceShutdown() throws InterruptedException { log.info("Forcing shutdown of thread {}", getName()); isRunning.set(false); interrupt(); } }