mirror of
https://github.com/berkeleydb/je.git
synced 2024-11-15 01:46:24 +00:00
327 lines
12 KiB
Java
327 lines
12 KiB
Java
/*-
|
||
* Copyright (C) 2002, 2017, 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.
|
||
*/
|
||
|
||
import java.io.File;
|
||
import java.util.concurrent.CountDownLatch;
|
||
|
||
import com.sleepycat.bind.tuple.IntegerBinding;
|
||
import com.sleepycat.bind.tuple.StringBinding;
|
||
import com.sleepycat.je.Database;
|
||
import com.sleepycat.je.DatabaseConfig;
|
||
import com.sleepycat.je.DatabaseEntry;
|
||
import com.sleepycat.je.DbInternal;
|
||
import com.sleepycat.je.Environment;
|
||
import com.sleepycat.je.EnvironmentConfig;
|
||
import com.sleepycat.je.EnvironmentStats;
|
||
import com.sleepycat.je.StatsConfig;
|
||
import com.sleepycat.je.evictor.Evictor;
|
||
|
||
/**
|
||
* Before 4.1.9, if users try to open multiple shared cache environments
|
||
* (with different enviroment home) in multiple threads, they may run into the
|
||
* issue that those shared cache environments are not sharing the cache.
|
||
*
|
||
* This is because we divide the DbEnvPool.getEnvironment into two
|
||
* synchronization blocks, for shared cache environment specially, the two
|
||
* blocks do different things:
|
||
* 1. First synchronization block looks for an existed shared cache environment
|
||
* before creating a new shared cache environment, so that it can reuse the
|
||
* cache.
|
||
* 2. Second synchroniation block will add a shared cache environment into the
|
||
* shared cache list after its recovery is done.
|
||
*
|
||
* This would bring issues when we open environments in multiple threads:
|
||
* 1. Thread A opens a shared cache environment, it doesn't find any shared
|
||
* cache environment instance in the list at that time
|
||
* 2. Thread A sleeps before it goes to the second synchronization block, so
|
||
* currently no shared cache environments in the list
|
||
* 3. Thread B starts to open another shared cache environment, it goes to the
|
||
* first block, find no instance in the list, it won't use the shared cache
|
||
*
|
||
* The above scenario results in multiple shared cache environments are using
|
||
* different cacches, and may result in OOME, see [#19165] for more details.
|
||
*
|
||
* This test simulates this case, it will open shared cache enviroments in
|
||
* different threads and do some database operations. When operations in all
|
||
* threads are finished, before closing the database, this test will check to
|
||
* see if the SharedCacheTotalBytes in each environment are the same, also it
|
||
* will check that all the environments are using the same Evictor.
|
||
*/
|
||
public class OpenEnvStress {
|
||
private static final String DIR_PREFIX = "env";
|
||
private static final String DB_NAME = "testDb";
|
||
/* Root dir for environment directories. */
|
||
private File envRoot;
|
||
/* Number of updating threads. */
|
||
private int numEnvs = 10;
|
||
/* Total update operations. */
|
||
private int totalIterations = 5000;
|
||
/* True if open Environments shared chace. */
|
||
private boolean sharedCache = false;
|
||
|
||
public static void main(String args[]) {
|
||
try {
|
||
OpenEnvStress test = new OpenEnvStress();
|
||
test.parseArgs(args);
|
||
test.doWork();
|
||
} catch (Throwable e) {
|
||
e.printStackTrace(System.out);
|
||
System.exit(-1);
|
||
}
|
||
}
|
||
|
||
/* Output command-line input arguments to log. */
|
||
private void printArgs(String[] args) {
|
||
System.out.print("\nCommand line arguments:");
|
||
for (String arg : args) {
|
||
System.out.print(' ');
|
||
System.out.print(arg);
|
||
}
|
||
System.out.println();
|
||
}
|
||
|
||
private void usage(String error) {
|
||
if (error != null) {
|
||
System.err.println(error);
|
||
}
|
||
System.err.println
|
||
("java " + getClass().getName() + '\n' +
|
||
" [-root <envRoot>] [-numEnvs <number of environments>]\n" +
|
||
" [-totalIterations <total iterations of open/close " +
|
||
"environments>]\n" +
|
||
" [-sharedCache <true if environments share cache>]\n");
|
||
System.exit(1);
|
||
}
|
||
|
||
private void parseArgs(String args[]) {
|
||
try {
|
||
if (args.length == 0) {
|
||
throw new IllegalArgumentException();
|
||
}
|
||
/* Parse command-line input arguments. */
|
||
for (int i = 0; i < args.length; i += 1) {
|
||
String arg = args[i];
|
||
boolean moreArgs = i < args.length - 1;
|
||
if (arg.equals("-root") && moreArgs) {
|
||
envRoot = new File(args[++i]);
|
||
} else if (arg.equals("-numEnvs") && moreArgs) {
|
||
numEnvs = Integer.parseInt(args[++i]);
|
||
} else if (arg.equals("-totalIterations") && moreArgs) {
|
||
totalIterations = Integer.parseInt(args[++i]);
|
||
} else if (arg.equals("-sharedCache") && moreArgs) {
|
||
sharedCache = new Boolean(args[++i]);
|
||
} else {
|
||
usage("Unknown arg: " + arg);
|
||
}
|
||
}
|
||
|
||
if (envRoot == null) {
|
||
throw new IllegalArgumentException
|
||
("-root is an required argument.");
|
||
}
|
||
|
||
if (numEnvs <= 0 || totalIterations <= 0) {
|
||
throw new IllegalArgumentException
|
||
("-numEnvs and -totalIterations require positive " +
|
||
"argument.");
|
||
}
|
||
|
||
printArgs(args);
|
||
} catch (IllegalArgumentException e) {
|
||
usage("IllegalArguments! ");
|
||
e.printStackTrace();
|
||
System.exit(1);
|
||
}
|
||
}
|
||
|
||
/* Do the work. */
|
||
public void doWork()
|
||
throws Exception {
|
||
|
||
System.out.println("Staring test.....");
|
||
|
||
/*
|
||
* Start multiple threads to open multiple Environments, to make sure
|
||
* the issue described in SR [#19165] is gone, it would be better to
|
||
* run this process multiple times.
|
||
*/
|
||
for (int i = 1; i <= totalIterations; i++) {
|
||
initEnvDirs(i == 1);
|
||
CountDownLatch startSignal = new CountDownLatch(1);
|
||
CountDownLatch endSignal = new CountDownLatch(numEnvs);
|
||
OpenEnvAndWorkThread[] threads = new OpenEnvAndWorkThread[numEnvs];
|
||
for (int j = 1; j <= numEnvs; j++) {
|
||
threads[j - 1] = new OpenEnvAndWorkThread
|
||
(startSignal, endSignal,
|
||
new File(envRoot, DIR_PREFIX + j), j);
|
||
threads[j - 1].start();
|
||
}
|
||
startSignal.countDown();
|
||
endSignal.await();
|
||
doSharedCacheCheck(threads);
|
||
for (int j = 1; j <= numEnvs; j++) {
|
||
threads[j - 1].closeEnv();
|
||
}
|
||
|
||
if (i % 1000 == 0) {
|
||
System.out.println("Iteration: " + i + " finishes.");
|
||
}
|
||
}
|
||
}
|
||
|
||
private void initEnvDirs(boolean create) {
|
||
/* Create Environment home directories for Environments. */
|
||
if (create) {
|
||
for (int i = 1; i <= numEnvs; i++) {
|
||
File envFile = new File(envRoot, DIR_PREFIX + i);
|
||
if (!envFile.exists()) {
|
||
assert envFile.mkdir();
|
||
}
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
/* Remove the log files in the Environment home directory. */
|
||
File[] files = envRoot.listFiles();
|
||
for (File file : files) {
|
||
if (file.isDirectory() && file.getName().contains(DIR_PREFIX)) {
|
||
File[] logFiles = file.listFiles();
|
||
for (File logFile : logFiles) {
|
||
assert logFile.delete();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Check that SharedCache Environments are using the same Evictor and the
|
||
* same shared cache.
|
||
*/
|
||
private void doSharedCacheCheck(OpenEnvAndWorkThread[] threads) {
|
||
if (!sharedCache) {
|
||
return;
|
||
}
|
||
|
||
Evictor evictor = null;
|
||
long sharedCacheBytes = 0;
|
||
for (OpenEnvAndWorkThread thread : threads) {
|
||
if (evictor == null) {
|
||
evictor = getEvictor(thread);
|
||
sharedCacheBytes = getSharedCacheBytes(thread);
|
||
} else {
|
||
final Evictor tmpEvictor = getEvictor(thread);
|
||
final long tmpSharedCacheBytes = getSharedCacheBytes(thread);
|
||
if (evictor == tmpEvictor &&
|
||
sharedCacheBytes == tmpSharedCacheBytes) {
|
||
continue;
|
||
}
|
||
|
||
throw new IllegalStateException
|
||
("SharedCache Environments in the same JVM don't really " +
|
||
"use the same cache" + ". First evictor is " + evictor +
|
||
". Current evictor is " + tmpEvictor + ". First " +
|
||
"sharedCacheBytes is " + sharedCacheBytes + ". Current " +
|
||
"sharedCacheBytes is " + tmpSharedCacheBytes);
|
||
}
|
||
}
|
||
}
|
||
|
||
private Evictor getEvictor(OpenEnvAndWorkThread thread) {
|
||
return DbInternal.getNonNullEnvImpl(thread.getEnv()).getEvictor();
|
||
}
|
||
|
||
/* Return the SharedCacheTotalBytes for an Environment. */
|
||
private long getSharedCacheBytes(OpenEnvAndWorkThread thread) {
|
||
StatsConfig stConfig = new StatsConfig();
|
||
stConfig.setFast(true);
|
||
|
||
EnvironmentStats envStats = thread.getEnv().getStats(stConfig);
|
||
|
||
return envStats.getSharedCacheTotalBytes();
|
||
}
|
||
|
||
/*
|
||
* The thread which open/close a shared cache Environment, also do some
|
||
* database operations to consume cache.
|
||
*/
|
||
class OpenEnvAndWorkThread extends Thread {
|
||
private final CountDownLatch start;
|
||
private final CountDownLatch end;
|
||
private final File envHome;
|
||
private final int threadIndex;
|
||
private Environment env;
|
||
private Database db;
|
||
|
||
public OpenEnvAndWorkThread(CountDownLatch start,
|
||
CountDownLatch end,
|
||
File envHome,
|
||
int threadIndex) {
|
||
this.start = start;
|
||
this.end = end;
|
||
this.envHome = envHome;
|
||
this.threadIndex = threadIndex;
|
||
}
|
||
|
||
public void run() {
|
||
try {
|
||
EnvironmentConfig envConfig = new EnvironmentConfig();
|
||
envConfig.setAllowCreate(true);
|
||
envConfig.setSharedCache(sharedCache);
|
||
envConfig.setConfigParam(EnvironmentConfig.VERIFY_BTREE, "false");
|
||
|
||
start.await();
|
||
|
||
env = new Environment(envHome, envConfig);
|
||
|
||
DatabaseConfig dbConfig = new DatabaseConfig();
|
||
dbConfig.setAllowCreate(true);
|
||
db = env.openDatabase(null, DB_NAME, dbConfig);
|
||
|
||
/*
|
||
* Insert different data sets in different environments to
|
||
* consume different cache, but we expect the same
|
||
* SharedCacheTotalBytes among all environments.
|
||
*/
|
||
DatabaseEntry key = new DatabaseEntry();
|
||
DatabaseEntry data = new DatabaseEntry();
|
||
for (int i = 1; i <= threadIndex * 100; i++) {
|
||
IntegerBinding.intToEntry(i, key);
|
||
StringBinding.stringToEntry("herococo", data);
|
||
db.put(null, key, data);
|
||
}
|
||
|
||
end.countDown();
|
||
} catch (Throwable e) {
|
||
e.printStackTrace();
|
||
System.exit(1);
|
||
}
|
||
}
|
||
|
||
/* Close the database and environments. */
|
||
public void closeEnv() {
|
||
if (db != null) {
|
||
db.close();
|
||
}
|
||
|
||
if (env != null) {
|
||
env.close();
|
||
}
|
||
}
|
||
|
||
/* Return the Environment handle. */
|
||
public Environment getEnv() {
|
||
return env;
|
||
}
|
||
}
|
||
}
|