blob: 02cd858ac0bf7b586e87a0b29f331536fdedb126 [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.util.verify;
import static com.sleepycat.je.config.EnvironmentParams.ENV_RUN_VERIFIER;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_BTREE;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_BTREE_BATCH_DELAY;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_BTREE_BATCH_SIZE;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_DATA_RECORDS;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_LOG;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_LOG_READ_DELAY;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_MAX_TARDINESS;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_OBSOLETE_RECORDS;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_SCHEDULE;
import static com.sleepycat.je.config.EnvironmentParams.VERIFY_SECONDARIES;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import com.sleepycat.je.CorruptSecondariesException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.VerifyConfig;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.util.DbVerifyLog;
import com.sleepycat.je.utilint.CronScheduleParser;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.PollCondition;
import com.sleepycat.je.utilint.StoppableThread;
/**
* Periodically perform checksum verification, Btree verification, or both,
* depending on {@link com.sleepycat.je.EnvironmentConfig#VERIFY_LOG} and
* {@link com.sleepycat.je.EnvironmentConfig#VERIFY_BTREE}.
*
* The first-time start time and the period of the verification is determined
* by {@link com.sleepycat.je.EnvironmentConfig#VERIFY_SCHEDULE}.
*/
public class DataVerifier {
private final EnvironmentImpl envImpl;
private final Timer timer;
private VerifyTask verifyTask;
private boolean verifyLog;
private boolean verifyBtree;
private final DbVerifyLog dbLogVerifier;
private final BtreeVerifier dbTreeVerifier;
private long verifyDelay;
private long verifyInterval;
private String cronSchedule;
private boolean shutdownRequest = false;
private final String VERIFIER_SCHEDULE = "test.je.env.verifierSchedule";
public DataVerifier(EnvironmentImpl envImpl) {
this.envImpl = envImpl;
this.timer = new Timer(
envImpl.makeDaemonThreadName(
Environment.DATA_CORRUPTION_VERIFIER_NAME),
true /*isDaemon*/);
dbLogVerifier = new DbVerifyLog(envImpl, 0);
dbTreeVerifier = new BtreeVerifier(envImpl);
}
/**
* Applies the new configuration, then cancels and reschedules the verify
* task as needed.
*/
public void configVerifyTask(DbConfigManager configMgr) {
if (!updateConfig(configMgr)) {
return;
}
synchronized (this) {
if (!shutdownRequest) {
cancel();
if (cronSchedule != null) {
verifyTask = new VerifyTask(envImpl);
/*
* Use Timer.scheduleAtFixedRate to instead of
* Timer.schedule, since this is a long running task and
* the next task is NOT expected to be scheduled for
* 24 hours later, it is expected to be scheduled at
* a fixed time.
*/
timer.scheduleAtFixedRate(
verifyTask, verifyDelay, verifyInterval);
}
}
}
}
private void cancel() {
if (verifyTask != null) {
verifyTask.cancel();
}
/*
* Stop verifier as soon as possible when it is disabled via
* EnvironmentMutableConfig.
*/
dbLogVerifier.setStopVerifyFlag(true);
dbTreeVerifier.setStopVerifyFlag(true);
}
public void requestShutdown() {
synchronized (this) {
shutdownRequest = true;
cancel();
timer.cancel();
}
}
public void shutdown() {
requestShutdown();
final int timeoutMs = 30000;
final PollCondition cond = new PollCondition(2, timeoutMs) {
@Override
protected boolean condition() {
/* Copy verifyTask since it may change in another thread. */
final VerifyTask task = verifyTask;
return task == null || !task.isRunning;
}
};
if (!cond.await()) {
LoggerUtils.warning(
envImpl.getLogger(), envImpl,
"Unable to shutdown data verifier after " + timeoutMs + "ms");
}
}
public long getVerifyDelay() {
return verifyDelay;
}
public long getVerifyInterval() {
return verifyInterval;
}
public VerifyTask getVerifyTask() {
return verifyTask;
}
public String getCronSchedule() {
return cronSchedule;
}
/**
* Applies the new configuration and returns whether it changed.
*/
private boolean updateConfig(DbConfigManager configMgr) {
/*
* If set to false (which is not the default).
*/
if (!configMgr.getBoolean(ENV_RUN_VERIFIER)) {
if (cronSchedule == null) {
return false;
}
cronSchedule = null;
verifyDelay = 0;
verifyInterval = 0;
return true;
} else {
String newCronSchedule = configMgr.get(VERIFY_SCHEDULE);
/*
* If the data verifier schedule is set by system property and
* it is not set through the JE source code explicitly, then
* the system property will be used.
*
* This is used for JE unit test and JE standalone test.
*/
String sysPropVerifySchedule =
System.getProperty(VERIFIER_SCHEDULE);
if (sysPropVerifySchedule != null &&
!configMgr.isSpecified(VERIFY_SCHEDULE)) {
newCronSchedule = sysPropVerifySchedule;
}
if (CronScheduleParser.checkSame(cronSchedule, newCronSchedule)) {
return false;
}
CronScheduleParser csp = new CronScheduleParser(newCronSchedule);
verifyDelay = csp.getDelayTime();
verifyInterval = csp.getInterval();
cronSchedule = newCronSchedule;
return true;
}
}
class VerifyTask extends TimerTask {
private final EnvironmentImpl envImpl;
private volatile boolean isRunning;
VerifyTask(EnvironmentImpl envImpl) {
this.envImpl = envImpl;
}
private void updateConfig() {
DbConfigManager configMgr = envImpl.getConfigManager();
verifyLog = configMgr.getBoolean(VERIFY_LOG);
verifyBtree = configMgr.getBoolean(VERIFY_BTREE);
dbLogVerifier.setReadDelay(
configMgr.getDuration(VERIFY_LOG_READ_DELAY),
TimeUnit.MILLISECONDS);
final VerifyConfig btreeVerifyConfig = new VerifyConfig();
btreeVerifyConfig.setVerifySecondaries(
configMgr.getBoolean(VERIFY_SECONDARIES));
btreeVerifyConfig.setVerifyDataRecords(
configMgr.getBoolean(VERIFY_DATA_RECORDS));
btreeVerifyConfig.setVerifyObsoleteRecords(
configMgr.getBoolean(VERIFY_OBSOLETE_RECORDS));
btreeVerifyConfig.setBatchSize(
configMgr.getInt(VERIFY_BTREE_BATCH_SIZE));
btreeVerifyConfig.setBatchDelay(
configMgr.getDuration(VERIFY_BTREE_BATCH_DELAY),
TimeUnit.MILLISECONDS);
dbTreeVerifier.setBtreeVerifyConfig(btreeVerifyConfig);
/*
* 1. Why call dbTreeVerifier.setVerifyFlag here, rather than
* immediately after call cancel in configVerifyTask?
* If we do that, then maybe before BtreeVerifier really gets
* the stop status of the flag, we set the status of the flag
* to be OK again. Then the previous task can not be stopped
* as soon as possible.
* If we do here, this means that one new task has already
* started, so the previous task has already been stopped.
* 2. Why use synchronized?
* Consider the following scenario:
* shutdown Thread task Thread
*
* !shutdownRequest
*
* shutdownRequest = true
* dbTreeVerifier.setStopVerifyFlag
*
* dbTreeVerifier.setVerifyFlag
*/
synchronized (DataVerifier.this) {
if (!shutdownRequest) {
dbLogVerifier.setStopVerifyFlag(false);
dbTreeVerifier.setStopVerifyFlag(false);
}
}
}
@Override
public void run() {
/*
* If the scheduled execution time of the scheduled run lags
* very far from the current time due to the long-time current
* verification, the scheduled run is just skipped.
*/
if (System.currentTimeMillis() - scheduledExecutionTime() >=
envImpl.getConfigManager().getDuration(VERIFY_MAX_TARDINESS)) {
return;
}
isRunning = true;
boolean success = false;
updateConfig();
try {
if (verifyLog) {
dbLogVerifier.verifyAll();
}
if (verifyBtree) {
dbTreeVerifier.verifyAll();
}
success = true;
} catch (EnvironmentFailureException|
CorruptSecondariesException e) {
/* Do nothing. Just cancel this timer in finally. */
} catch (Throwable e) {
if (envImpl.isValid()) {
StoppableThread.handleUncaughtException(
envImpl.getLogger(), envImpl, Thread.currentThread(),
e);
}
} finally {
if (!success) {
requestShutdown();
}
isRunning = false;
}
}
}
}