blob: 0afcfcfd564ae2a7374a7a1291de417b776bd142 [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.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.ExceptionListener;
import com.sleepycat.je.LockConflictException;
import com.sleepycat.je.dbi.EnvironmentFailureReason;
import com.sleepycat.je.dbi.EnvironmentImpl;
/**
* A daemon thread. Also see StoppableThread for an alternative daemon
* construct.
*/
public abstract class DaemonThread implements DaemonRunner, Runnable {
private static final int JOIN_MILLIS = 10;
private volatile long waitTime;
private final Object synchronizer = new Object();
private Thread thread;
protected String name;
protected int nWakeupRequests;
public static boolean stifleExceptionChatter = false;
/* Fields shared between threads must be 'volatile'. */
private volatile boolean shutdownRequest = false;
private volatile boolean paused = false;
/* This is not volatile because it is only an approximation. */
private boolean running = false;
/* Fields for DaemonErrorListener, enabled only during testing. */
protected final EnvironmentImpl envImpl;
private static final String ERROR_LISTENER = "setErrorListener";
/* Logger used in DaemonThread's subclasses. */
protected final Logger logger;
public DaemonThread(final long waitTime,
final String name,
final EnvironmentImpl envImpl) {
this.waitTime = waitTime;
this.name = envImpl.makeDaemonThreadName(name);
this.envImpl = envImpl;
this.logger = createLogger();
}
protected Logger createLogger() {
return LoggerUtils.getLogger(getClass());
}
/**
* For testing.
*/
public Thread getThread() {
return thread;
}
/**
* If run is true, starts the thread if not started or unpauses it
* if already started; if run is false, pauses the thread if
* started or does nothing if not started.
*
* Note that no thread is created unless run is true at some time. That
* way, threads are conserved in cases where the app wants to run their
* own threads. This can be important when many JE envs are in the same
* process, in which case a shared cache is often used.
*/
public void runOrPause(boolean run) {
if (run) {
paused = false;
if (thread != null) {
wakeup();
} else {
thread = new Thread(this, name);
thread.setDaemon(true);
thread.start();
}
} else {
paused = true;
}
}
public void requestShutdown() {
shutdownRequest = true;
}
/**
* Requests shutdown and calls join() to wait for the thread to stop.
*/
public void shutdown() {
if (thread != null) {
shutdownRequest = true;
while (thread.isAlive()) {
synchronized (synchronizer) {
synchronizer.notifyAll();
}
try {
thread.join(JOIN_MILLIS);
} catch (InterruptedException e) {
/*
* Klockwork - ok
* Don't say anything about exceptions here.
*/
}
}
thread = null;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<DaemonThread name=\"").append(name).append("\"/>");
return sb.toString();
}
public void wakeup() {
if (!paused) {
synchronized (synchronizer) {
synchronizer.notifyAll();
}
}
}
public void run() {
while (!shutdownRequest) {
try {
/* Do a unit of work. */
int numTries = 0;
long maxRetries = nDeadlockRetries();
while (numTries <= maxRetries &&
!shutdownRequest &&
!paused) {
try {
nWakeupRequests++;
running = true;
onWakeup();
break;
} catch (LockConflictException e) {
} finally {
running = false;
}
numTries++;
}
/* Wait for notify, timeout or interrupt. */
if (!shutdownRequest) {
synchronized (synchronizer) {
if (waitTime == 0 || paused) {
synchronizer.wait();
} else {
synchronizer.wait(waitTime);
}
}
}
} catch (InterruptedException e) {
notifyExceptionListener(e);
if (!stifleExceptionChatter) {
logger.info
("Shutting down " + this + " due to exception: " + e);
}
shutdownRequest = true;
assert checkErrorListener(e);
} catch (Exception e) {
notifyExceptionListener(e);
if (!stifleExceptionChatter) {
/*
* If the exception caused the environment to become
* invalid, then shutdownRequest will have been set to true
* by EnvironmentImpl.invalidate, which is called by the
* EnvironmentFailureException ctor.
*/
logger.log(Level.SEVERE,
this.toString() + " caught exception, " + e +
(shutdownRequest ? " Exiting" : " Continuing"),
e);
}
assert checkErrorListener(e);
} catch (Error e) {
assert checkErrorListener(e);
envImpl.invalidate(e); /* [#21929] */
notifyExceptionListener(envImpl.getInvalidatingException());
/*
* Since there is no uncaught exception handler (yet) we
* shutdown the thread here and log the exception.
*/
shutdownRequest = true;
logger.log(Level.SEVERE, "Error caught in " + this, e);
}
}
}
private void notifyExceptionListener(Exception e) {
if (envImpl == null) {
return;
}
final ExceptionListener listener = envImpl.getExceptionListener();
if (listener == null) {
return;
}
listener.exceptionThrown(DbInternal.makeExceptionEvent(e, name));
}
/**
* If Daemon Thread throws errors and exceptions, this function will catch
* it and throw a EnvironmentFailureException, and fail the test.
*
* Only used during testing.
*/
public boolean checkErrorListener(Throwable e) {
if (Boolean.getBoolean(ERROR_LISTENER)) {
if (!stifleExceptionChatter) {
logger.severe(name + " " + LoggerUtils.getStackTrace(e));
}
new EnvironmentFailureException
(envImpl, EnvironmentFailureReason.TEST_INVALIDATE,
"Daemon thread failed during testing", e);
}
return true;
}
/**
* Returns the number of retries to perform when Deadlock Exceptions
* occur.
*/
protected long nDeadlockRetries() {
return 0;
}
/**
* onWakeup is synchronized to ensure that multiple invocations of the
* DaemonThread aren't made.
*/
abstract protected void onWakeup()
throws DatabaseException;
/**
* Returns whether shutdown has been requested. This method should be
* used to to terminate daemon loops.
*/
protected boolean isShutdownRequested() {
return shutdownRequest;
}
/**
* Returns whether the daemon is currently paused/disabled. This method
* should be used to to terminate daemon loops.
*/
protected boolean isPaused() {
return paused;
}
/**
* Returns whether the onWakeup method is currently executing. This is
* only an approximation and is used to avoid unnecessary wakeups.
*/
public boolean isRunning() {
return running;
}
public void setWaitTime(long waitTime) {
this.waitTime = waitTime;
}
/**
* For unit testing.
*/
public int getNWakeupRequests() {
return nWakeupRequests;
}
}