blob: 70c2ddbad7e07275783ea8fa240ad44abe611ffa [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je.utilint;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.EnvironmentWedgedException;
import com.sleepycat.je.ExceptionListener;
import com.sleepycat.je.dbi.EnvironmentFailureReason;
import com.sleepycat.je.dbi.EnvironmentImpl;
/**
* A StoppableThread is a daemon that obeys the following mandates:
* - it sets the daemon property for the thread
* - an uncaught exception handler is always registered
* - the thread registers with the JE exception listener mechanism.
* - its shutdown method can only be executed once. StoppableThreads are not
* required to implement shutdown() methods, because in some cases their
* shutdown processing must be coordinated by an owning, parent thread.
*
* StoppableThread is an alternative to the DaemonThread. It also assumes that
* the thread's run() method may be more complex than that of the work-queue,
* task oriented DaemonThread.
*
* A StoppableThread's run method should catch and handle all exceptions. By
* default, unhandled exceptions are considered programming errors, and
* invalidate the environment, but StoppableThreads may supply alternative
* uncaught exception handling.
*
* StoppableThreads usually are created with an EnvironmentImpl, but on
* occasion an environment may not be available (for components that can
* execute without an environment). In that case, the thread obviously does not
* invalidate the environment.
*
* Note that the StoppableThread.cleanup must be invoked upon, or soon after,
* thread exit.
*/
public abstract class StoppableThread extends Thread {
/* The environment, if any, that's associated with this thread. */
protected final EnvironmentImpl envImpl;
/*
* Shutdown can only be executed once. The shutdown field protects against
* multiple invocations.
*/
private final AtomicBoolean shutdown = new AtomicBoolean(false);
/* The exception (if any) that forced this node to shut down. */
private Exception savedShutdownException = null;
/* Total cpu time used by thread */
private long totalCpuTime = -1;
/* Total user time used by thread */
private long totalUserTime = -1;
/**
* The default wait period for an interrupted thread to exit as part of a
* hard shutdown.
*/
private static final int DEFAULT_INTERRUPT_WAIT_MS = 10 * 1000;
/**
* The wait period for joining a thread in which shutdown is running.
* Use a large timeout since we want the shutdown to complete normally,
* if at all possible.
*/
private static final int WAIT_FOR_SHUTDOWN_MS =
DEFAULT_INTERRUPT_WAIT_MS * 3;
protected StoppableThread(final String threadName) {
this(null, null, null, threadName);
}
protected StoppableThread(final EnvironmentImpl envImpl,
final String threadName) {
this(envImpl, null /* handler */, null /* runnable */,threadName);
}
protected StoppableThread(final EnvironmentImpl envImpl,
final UncaughtExceptionHandler handler,
final String threadName) {
this(envImpl, handler, null /* runnable */, threadName);
}
protected StoppableThread(final EnvironmentImpl envImpl,
final UncaughtExceptionHandler handler,
final Runnable runnable,
final String threadName) {
super(null, runnable, threadName);
this.envImpl = envImpl;
/*
* Set the daemon property so this thread will not hang up the
* application.
*/
setDaemon(true);
setUncaughtExceptionHandler
((handler == null) ? new UncaughtHandler() : handler);
}
/**
* @return a logger to use when logging uncaught exceptions.
*/
abstract protected Logger getLogger();
/**
* Returns the exception if any that provoked the shutdown
*
* @return the exception, or null if it was a normal shutdown
*/
public Exception getSavedShutdownException() {
return savedShutdownException;
}
public void saveShutdownException(Exception shutdownException) {
savedShutdownException = shutdownException;
}
public boolean isShutdown() {
return shutdown.get();
}
/**
* If the shutdown flag is false, set it to true and return false; in this
* case the caller should perform shutdown, including calling {@link
* #shutdownThread}. If the shutdown flag is true, wait for this thread to
* exit and return true; in this case the caller should not perform
* shutdown.
*
* When shutdownDone is initially called by thread X (including from the
* run method of the thread being shutdown), then a thread Y calling
* shutdownDone should simply return without performing shutdown (this is
* when shutdownDone returns true). In this case it is important that this
* method calls {@link #waitForExit} in thread Y to ensure that thread X
* really dies, or that an EnvironmentWedgedException is thrown if X does
* not die. In particular it is important that all JE threads have died and
* released their resources when Environment.close returns to the app
* thread, or that EWE is thrown if any JE threads have not died. This
* allows the app to reliably re-open the env, or exit the process if
* necessary. [#25648]
*
* Note than when thread X has sub-components and manages their threads,
* thread X's shutdown method will call shutdown for its managed threads.
* Waiting for exit of thread X will therefore wait for exit of its managed
* threads, assuming that all shutdown methods calls shutdownDone as
* described.
*
* @param logger the logger on which to log messages
*
* @return true if shutdown is already set.
*/
protected boolean shutdownDone(Logger logger) {
if (shutdown.compareAndSet(false, true)) {
return false;
}
waitForExit(logger);
return true;
}
/**
* Must be invoked upon, or soon after, exit from the thread to perform
* any cleanup, and ensure that any allocated resources are freed.
*/
protected void cleanup() {
}
/*
* A static method to handle the uncaught exception. This method
* can be called in other places, such as in FileManager.
*
* When an uncaught exception occurs, log it, publish it to the
* exception handler, and invalidate the environment.
*/
public static void handleUncaughtException(
final Logger useLogger,
final EnvironmentImpl envImpl,
final Thread t,
final Throwable e) {
if (useLogger != null) {
String envName = (envImpl == null)? "" : envImpl.getName();
String message = envName + ":" + t.getName() +
" exited unexpectedly with exception " + e;
if (e != null) {
message += LoggerUtils.getStackTrace(e);
}
if (envImpl != null) {
/*
* If we have an environment, log this to all three
* handlers.
*/
LoggerUtils.severe(useLogger, envImpl, message);
} else {
/*
* We don't have an environment, but at least log this
* to the console.
*/
useLogger.log(Level.SEVERE, message);
}
}
if (envImpl == null) {
return;
}
/*
* If not already invalid, invalidate environment by creating an
* EnvironmentFailureException.
*/
if (envImpl.isValid()) {
/*
* Create the exception to invalidate the environment, but do
* not throw it since the handle is invoked in some internal
* JVM thread and the exception is not meaningful to the
* invoker.
*/
@SuppressWarnings("unused")
EnvironmentFailureException unused =
new EnvironmentFailureException
(envImpl, EnvironmentFailureReason.UNCAUGHT_EXCEPTION,
e);
}
final ExceptionListener exceptionListener =
envImpl.getExceptionListener();
if (exceptionListener != null) {
exceptionListener.exceptionThrown(
DbInternal.makeExceptionEvent(
envImpl.getInvalidatingException(), t.getName()));
}
}
/**
* An uncaught exception should invalidate the environment. Check if the
* environmentImpl is null, because there are a few cases where a
* StoppableThread is created for components that work both in replicated
* nodes and independently.
*/
private class UncaughtHandler implements UncaughtExceptionHandler {
/**
* When an uncaught exception occurs, log it, publish it to the
* exception handler, and invalidate the environment.
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
Logger useLogger = getLogger();
handleUncaughtException(useLogger, envImpl, t, e);
}
}
/**
* This method is invoked from another thread of control to shutdown this
* thread. The method tries shutting down the thread using a variety of
* techniques, starting with the gentler techniques in order to limit of
* stopping the thread on the overall process and proceeding to harsher
* techniques:
*
* 1) It first tries a "soft" shutdown by invoking
* <code>initiateSoftShutdown()</code>. This is the technique of choice.
* Each StoppableThread is expected to make provisions for a clean shutdown
* via this method. The techniques used to implement this method may vary
* based upon the specifics of the thread.
*
* 2) If that fails it interrupts the thread.
*
* 3) If the thread does not respond to the interrupt, it invalidates the
* environment.
*
* All Stoppable threads are expected to catch an interrupt, clean up and
* then exit. The cleanup may involve invalidation of the environment, if
* the thread is not in a position to handle the interrupt cleanly.
*
* If the method has to resort to step 3, it means that thread and other
* resources may not have been freed and it would be best to exit and
* restart the process itself to ensure they are freed. In this case an
* EnvironmentWedgedException is used to invalidate the env, and the EWE
* will be thrown when the app calls Environment.close.
*
* @param logger the logger on which to log messages
*/
public void shutdownThread(Logger logger) {
/*
* Save resource usage, since it will not be available once the
* thread has exited.
*/
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
if (threadBean.isThreadCpuTimeSupported()) {
totalCpuTime = threadBean.getThreadCpuTime(getId());
totalUserTime = threadBean.getThreadUserTime(getId());
} else if (threadBean.isCurrentThreadCpuTimeSupported() &&
Thread.currentThread() == this) {
totalCpuTime = threadBean.getCurrentThreadCpuTime();
totalUserTime = threadBean.getCurrentThreadUserTime();
}
if (Thread.currentThread() == this) {
/* Shutdown was called from this thread's run method. */
return;
}
try {
LoggerUtils.info(logger, envImpl,
getName() + " soft shutdown initiated.");
final int waitMs = initiateSoftShutdown();
/*
* Wait for a soft shutdown to take effect, the preferred method
* for thread shutdown.
*/
if (waitMs >= 0) {
join(waitMs);
}
if (!isAlive()) {
LoggerUtils.fine(logger, envImpl, this + " has exited.");
return;
}
LoggerUtils.warning(
logger, envImpl,
"Soft shutdown failed for thread:" + this +
" after waiting for " + waitMs +
"ms resorting to interrupt.");
interrupt();
/*
* The thread must make provision to handle and exit on an
* interrupt.
*/
final long joinWaitTime =
(waitMs > 0) ? 2 * waitMs : DEFAULT_INTERRUPT_WAIT_MS;
join(joinWaitTime);
if (!isAlive()) {
LoggerUtils.warning(logger, envImpl,
this + " shutdown via interrupt.");
return;
}
/*
* Failed to shutdown thread despite all attempts. It's
* possible that the thread has a bug and/or is unable to
* to get to an interruptible point.
*/
final String msg = this +
" shutdown via interrupt FAILED. " +
"Thread still alive despite waiting for " +
joinWaitTime + "ms.";
LoggerUtils.severe(logger, envImpl, msg);
LoggerUtils.fullThreadDump(logger, envImpl, Level.SEVERE);
if (envImpl != null) {
@SuppressWarnings("unused")
EnvironmentFailureException unused =
new EnvironmentWedgedException(envImpl, msg);
}
} catch (InterruptedException e1) {
LoggerUtils.warning(
logger, envImpl,
"Interrupted while shutting down thread:" + this);
}
}
/**
* Used to wait for thread shutdown, when {@link #shutdownDone} returns
* true because it has been called by another thread.
*/
private void waitForExit(Logger logger) {
assert shutdown.get();
if (Thread.currentThread() == this) {
/* Shutdown was called from this thread's run method. */
return;
}
try {
join(WAIT_FOR_SHUTDOWN_MS);
if (!isAlive()) {
return;
}
/*
* For some reason, shutdown has not finished. This is unlikely,
* but possible. As in shutdownThread, we try interrupting the
* thread before giving up.
*/
LoggerUtils.warning(
logger, envImpl,
"Soft shutdown failed for thread:" + this +
" after waiting for " + WAIT_FOR_SHUTDOWN_MS +
"ms, resorting to interrupt in wait-for-shutdown.");
interrupt();
join(WAIT_FOR_SHUTDOWN_MS);
if (!isAlive()) {
return;
}
/*
* Failed to shutdown thread despite all attempts. It's
* possible that the thread has a bug and/or is unable to
* to get to an interruptible point.
*/
final String msg = this +
" shutdown via interrupt FAILED during wait-for-shutdown. " +
"Thread still alive despite waiting for " +
WAIT_FOR_SHUTDOWN_MS + "ms.";
LoggerUtils.severe(logger, envImpl, msg);
LoggerUtils.fullThreadDump(logger, envImpl, Level.SEVERE);
if (envImpl != null) {
@SuppressWarnings("unused")
EnvironmentFailureException unused =
new EnvironmentWedgedException(envImpl, msg);
}
} catch (InterruptedException e1) {
LoggerUtils.warning(
logger, envImpl,
"Interrupted during wait-for-shutdown:" + this);
}
}
/**
* Threads that use shutdownThread() must define this method. It's invoked
* by shutdownThread as an attempt at a soft shutdown.
*
* This method makes provisions for this thread to exit on its own. The
* technique used to make the thread exit can vary based upon the nature of
* the service being provided by the thread. For example, the thread may be
* known to poll some shutdown flag on a periodic basis, or it may detect
* that a channel that it waits on has been closed by this method.
*
* @return the amount of time in ms that the shutdownThread method will
* wait for the thread to exit. A -ve value means that the method will not
* wait. A zero value means it will wait indefinitely.
*/
protected int initiateSoftShutdown() {
return -1;
}
/**
* Returns the total cpu time associated with the thread, after the thread
* has been shutdown.
*/
public long getTotalCpuTime() {
return totalCpuTime;
}
/**
* Returns the total cpu time associated with the thread, after the thread
* has been shutdown.
*/
public long getTotalUserTime() {
return totalUserTime;
}
}