blob: 47980a60b0b7cdc11ac17e943ae8f8d830333462 [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.latch;
import static com.sleepycat.je.EnvironmentFailureException.unexpectedState;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.utilint.DatabaseUtil;
import com.sleepycat.je.utilint.LoggerUtils;
/**
* Supports latch debugging.
*
* In JE test mode (when the JE_TEST system property is set), TRACK_LATCHES
* will be true and related debugging methods may be used to check the number
* of Btree latches held.
*
* CAPTURE_OWNER is also set to true if the system property
* JE_CAPTURE_LATCH_OWNER is defined to true. This will capture a stack trace
* when a latch is acquired exclusively, and the stack trace will be included
* in all error messages. Capturing the stack trace is expensive so this is
* off by default for unit testing.
*/
public class LatchSupport {
public static final boolean TRACK_LATCHES = DatabaseUtil.TEST;
static final boolean CAPTURE_OWNER =
Boolean.getBoolean("JE_CAPTURE_LATCH_OWNER");
/*
* Indicates whether to use tryLock() with a timeout, instead of a simple
* lock() that waits forever and is uninterruptible. We would like to
* always use timeouts and interruptible latches, but these are new
* features and this boolean allows reverting to the old behavior.
*/
static final boolean INTERRUPTIBLE_WITH_TIMEOUT = true;
/* Used for Btree latches. */
public final static LatchTable btreeLatchTable =
TRACK_LATCHES ? (new LatchTable()) : null;
/* Used for all other latches. */
public final static LatchTable otherLatchTable =
TRACK_LATCHES ? (new LatchTable()) : null;
public static void expectBtreeLatchesHeld(final int expectNLatches) {
expectBtreeLatchesHeld(expectNLatches, "");
}
/* Used for SizeOf. */
public static final LatchContext DUMMY_LATCH_CONTEXT = new LatchContext() {
@Override
public int getLatchTimeoutMs() {
return 0;
}
@Override
public String getLatchName() {
return null;
}
@Override
public LatchTable getLatchTable() {
return null;
}
@Override
public EnvironmentImpl getEnvImplForFatalException() {
return null;
}
};
public static void expectBtreeLatchesHeld(final int expectNLatches,
final String msg) {
final int nHeld = btreeLatchTable.nLatchesHeld();
if (nHeld == expectNLatches) {
return;
}
throw unexpectedState(String.format(
"Expected %d Btree latches held but got %d. %s\nLatch table: %s\n",
expectNLatches, nHeld, msg, btreeLatchesHeldToString()));
}
public static int nBtreeLatchesHeld() {
return btreeLatchTable.nLatchesHeld();
}
public static void dumpBtreeLatchesHeld() {
System.out.println(btreeLatchesHeldToString());
}
public static String btreeLatchesHeldToString() {
return btreeLatchTable.latchesHeldToString();
}
/**
* Should be called when closing the environment, so that residual latches
* don't impact another environment that is opened
*/
public static void clear() {
if (TRACK_LATCHES) {
btreeLatchTable.clear();
otherLatchTable.clear();
}
}
/**
* Record debug info when a latch is acquired.
*/
static void trackAcquire(final Latch latch, final LatchContext context) {
final LatchTable latchTable = context.getLatchTable();
if (latchTable == null) {
return;
}
if (!latchTable.add(latch)) {
throw unexpectedState(
"Latch already held." + latch.debugString());
}
}
/**
* Record debug info when a latch is released.
*/
static void trackRelease(final Latch latch, final LatchContext context) {
final LatchTable latchTable = context.getLatchTable();
if (latchTable == null) {
return;
}
if (!latchTable.remove(latch)) {
throw unexpectedState(
"Latch not held." + latch.debugString());
}
}
static String toString(final Latch latch,
final LatchContext context,
final OwnerInfo lastOwnerInfo) {
final StringBuilder builder = new StringBuilder();
builder.append(context.getLatchName()).
append(" exclusiveOwner: ").
append(latch.getExclusiveOwner());
if (lastOwnerInfo != null) {
lastOwnerInfo.toString(builder);
}
return builder.toString();
}
static String debugString(final Latch latch,
final LatchContext context,
final OwnerInfo lastOwnerInfo) {
final StringBuilder builder = new StringBuilder(500);
builder.append(context.getLatchName());
builder.append(" currentThread: ");
builder.append(Thread.currentThread());
builder.append(" currentTime: ");
builder.append(System.currentTimeMillis());
if (TRACK_LATCHES) {
final LatchTable latchTable = context.getLatchTable();
if (latchTable != null) {
builder.append(" allLatchesHeld: (");
builder.append(latchTable.latchesHeldToString());
builder.append(")");
}
}
builder.append(" exclusiveOwner: ");
final Thread ownerThread = latch.getExclusiveOwner();
if (ownerThread != null) {
builder.append(ownerThread);
if (lastOwnerInfo != null) {
lastOwnerInfo.toString(builder);
}
} else {
builder.append("-none-");
}
return builder.toString();
}
static EnvironmentFailureException handleTimeout(
final Latch latch,
final LatchContext context) {
final EnvironmentImpl envImpl = context.getEnvImplForFatalException();
final Logger logger = envImpl.getLogger();
final String msg = latch.debugString();
LoggerUtils.logMsg(
logger, envImpl, Level.SEVERE,
"Thread dump follows for latch timeout: " + msg);
LoggerUtils.fullThreadDump(logger, envImpl, Level.SEVERE);
return unexpectedState(
envImpl, "Latch timeout. " + msg);
}
}