Implemented JavaWorkload
This change allows a user to write a workload in Java. The way this is implemented is by creating a JVM within the simulator and calling the corresponding workload class. A workload can then run in the simulator or on a testing cluster. If the workload is executed within the simulator, the resulting test will not be deterministic anymore as it will execute in a different thread (and even without that it is not clear, whether we could get determinism as the JVM does a lot of stuff that are not deterministic). This is intendet to get better testing of the Java client and layer authors can use the simulator to test their layers on a single machine but they can still simulate failing machines etc.
This commit is contained in:
parent
c532a00f30
commit
e23e63c6ac
|
@ -51,6 +51,8 @@ set(JAVA_BINDING_SRCS
|
|||
src/main/com/apple/foundationdb/subspace/Subspace.java
|
||||
src/main/com/apple/foundationdb/Transaction.java
|
||||
src/main/com/apple/foundationdb/TransactionContext.java
|
||||
src/main/com/apple/foundationdb/testing/AbstractWorkload.java
|
||||
src/main/com/apple/foundationdb/testing/WorkloadContext.java
|
||||
src/main/com/apple/foundationdb/tuple/ByteArrayUtil.java
|
||||
src/main/com/apple/foundationdb/tuple/IterableComparator.java
|
||||
src/main/com/apple/foundationdb/tuple/package-info.java
|
||||
|
@ -127,6 +129,9 @@ target_include_directories(fdb_java PRIVATE ${JNI_INCLUDE_DIRS})
|
|||
target_link_libraries(fdb_java PRIVATE fdb_c)
|
||||
set_target_properties(fdb_java PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/${SYSTEM_NAME}/amd64/)
|
||||
if(APPLE)
|
||||
set_target_properties(fdb_java PROPERTIES SUFFIX ".jnilib")
|
||||
endif()
|
||||
|
||||
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
|
||||
set(CMAKE_JNI_TARGET TRUE)
|
||||
|
@ -175,25 +180,34 @@ if(NOT OPEN_FOR_IDE)
|
|||
endif()
|
||||
if(WIN32)
|
||||
set(lib_destination "windows/amd64")
|
||||
set(clib_destination "windows/amd64/fdb_c.dll")
|
||||
elseif(APPLE)
|
||||
set(lib_destination "osx/x86_64")
|
||||
set(clib_destination "osx/x86_64/libfdb_c.jnilib")
|
||||
else()
|
||||
set(lib_destination "linux/amd64")
|
||||
set(clib_destination "linux/amd64/libfdb_c.so")
|
||||
endif()
|
||||
set(lib_destination "${unpack_dir}/lib/${lib_destination}")
|
||||
set(clib_destination "${unpack_dir}/lib/${clib_destination}")
|
||||
file(MAKE_DIRECTORY ${lib_destination})
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib_copied
|
||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:fdb_java> ${lib_destination} &&
|
||||
${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/lib_copied
|
||||
COMMENT "Copy library")
|
||||
add_custom_target(copy_lib DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/lib_copied)
|
||||
COMMENT "Copy jni library for fat jar")
|
||||
add_custom_command(OUTPUT ${clib_destination}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:fdb_c> ${clib_destination}
|
||||
COMMENT "Copy fdbc for fat jar")
|
||||
add_custom_target(copy_lib DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/lib_copied ${clib_destination})
|
||||
add_dependencies(copy_lib unpack_jar)
|
||||
set(target_jar ${jar_destination}/fdb-java-${CMAKE_PROJECT_VERSION}.jar)
|
||||
add_custom_command(OUTPUT ${target_jar}
|
||||
COMMAND ${Java_JAR_EXECUTABLE} cf ${target_jar} .
|
||||
WORKING_DIRECTORY ${unpack_dir}
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/lib_copied ${clib_destination}
|
||||
COMMENT "Build ${jar_destination}/fdb-java-${CMAKE_PROJECT_VERSION}.jar")
|
||||
add_custom_target(fat-jar DEPENDS ${target_jar})
|
||||
add_custom_target(fat-jar ALL DEPENDS ${target_jar})
|
||||
add_dependencies(fat-jar fdb-java)
|
||||
add_dependencies(fat-jar copy_lib)
|
||||
add_dependencies(packages fat-jar)
|
||||
endif()
|
||||
|
|
|
@ -104,10 +104,15 @@ public class FDB {
|
|||
* Called only once to create the FDB singleton.
|
||||
*/
|
||||
private FDB(int apiVersion) {
|
||||
this.apiVersion = apiVersion;
|
||||
this(apiVersion, true);
|
||||
}
|
||||
|
||||
private FDB(int apiVersion, boolean controlRuntime) {
|
||||
this.apiVersion = apiVersion;
|
||||
options = new NetworkOptions(this::Network_setOption);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::stopNetwork));
|
||||
if (controlRuntime) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::stopNetwork));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,7 +176,14 @@ public class FDB {
|
|||
*
|
||||
* @return the FoundationDB API object
|
||||
*/
|
||||
public static synchronized FDB selectAPIVersion(final int version) throws FDBException {
|
||||
public static FDB selectAPIVersion(final int version) throws FDBException {
|
||||
return selectAPIVersion(version, true);
|
||||
}
|
||||
|
||||
/**
|
||||
This function is called from C++ if the VM is controlled directly from FDB
|
||||
*/
|
||||
private static synchronized FDB selectAPIVersion(final int version, boolean controlRuntime) throws FDBException {
|
||||
if(singleton != null) {
|
||||
if(version != singleton.getAPIVersion()) {
|
||||
throw new IllegalArgumentException(
|
||||
|
@ -185,7 +197,7 @@ public class FDB {
|
|||
throw new IllegalArgumentException("API version not supported (maximum 610)");
|
||||
|
||||
Select_API_version(version);
|
||||
FDB fdb = new FDB(version);
|
||||
FDB fdb = new FDB(version, controlRuntime);
|
||||
|
||||
return singleton = fdb;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* AbstractWorkload.java
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2019 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.apple.foundationdb.testing;
|
||||
|
||||
import com.apple.foundationdb.Database;
|
||||
import com.apple.foundationdb.FDB;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractWorkload {
|
||||
protected WorkloadContext context;
|
||||
private ThreadPoolExecutor executorService;
|
||||
|
||||
public AbstractWorkload(WorkloadContext context) {
|
||||
this.context = context;
|
||||
executorService =
|
||||
new ThreadPoolExecutor(1, 2,
|
||||
10, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<>()) {
|
||||
@Override
|
||||
protected void beforeExecute(Thread t, Runnable r) {
|
||||
setProcessID(context.getProcessID());
|
||||
super.beforeExecute(t, r);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Executor getExecutor() {
|
||||
return executorService;
|
||||
}
|
||||
|
||||
public abstract void setup(Database db);
|
||||
public abstract void start(Database db);
|
||||
public abstract boolean check(Database db);
|
||||
public double getCheckTimeout() {
|
||||
return 3000;
|
||||
}
|
||||
|
||||
public void spanThread(Runnable runnable) {
|
||||
getExecutor().execute(runnable);
|
||||
}
|
||||
|
||||
private void setup(Database db, long voidCallback) {
|
||||
AbstractWorkload self = this;
|
||||
spanThread(new Runnable(){
|
||||
public void run() {
|
||||
self.setup(db);
|
||||
self.sendVoid(voidCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
private void start(Database db, long voidCallback) {
|
||||
AbstractWorkload self = this;
|
||||
spanThread(new Runnable(){
|
||||
public void run() {
|
||||
self.start(db);
|
||||
self.sendVoid(voidCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
private void check(Database db, long boolCallback) {
|
||||
AbstractWorkload self = this;
|
||||
spanThread(new Runnable(){
|
||||
public void run() {
|
||||
boolean res = self.check(db);
|
||||
self.sendBool(boolCallback, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
public native void log(int severity, String message, Map<String, String> details);
|
||||
private native void setProcessID(long processID);
|
||||
private native void sendVoid(long handle);
|
||||
private native void sendBool(long handle, boolean value);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* IWorkload.java
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2019 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.apple.foundationdb.testing;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class WorkloadContext {
|
||||
private Map<String, String> options;
|
||||
private int clientId, clientCount;
|
||||
long sharedRandomNumber, processID;
|
||||
|
||||
public WorkloadContext(Map<String, String> options, int clientId, int clientCount, long sharedRandomNumber, long processID)
|
||||
{
|
||||
this.options = options;
|
||||
this.clientId = clientId;
|
||||
this.clientCount = clientCount;
|
||||
this.sharedRandomNumber = sharedRandomNumber;
|
||||
this.processID = processID;
|
||||
}
|
||||
|
||||
public String getOption(String name, String defaultValue) {
|
||||
if (options.containsKey(name)) {
|
||||
return options.get(name);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public int getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public int getClientCount() {
|
||||
return clientCount;
|
||||
}
|
||||
|
||||
public long getSharedRandomNumber() {
|
||||
return sharedRandomNumber;
|
||||
}
|
||||
|
||||
public long getProcessID() {
|
||||
return processID;
|
||||
}
|
||||
}
|
|
@ -309,8 +309,8 @@ public:
|
|||
virtual flowGlobalType global(int id) { return getCurrentProcess()->global(id); };
|
||||
virtual void setGlobal(size_t id, flowGlobalType v) { getCurrentProcess()->setGlobal(id,v); };
|
||||
|
||||
protected:
|
||||
static thread_local ProcessInfo* currentProcess;
|
||||
protected:
|
||||
Mutex mutex;
|
||||
|
||||
private:
|
||||
|
|
|
@ -177,6 +177,18 @@ set(FDBSERVER_SRCS
|
|||
workloads/WriteBandwidth.actor.cpp
|
||||
workloads/WriteDuringRead.actor.cpp)
|
||||
|
||||
set(java_workload_docstring "Build the Java workloads (makes fdbserver link against JNI)")
|
||||
if(FDB_RELEASE)
|
||||
set(WITH_JAVA_WORKLOAD OFF CACHE BOOL "${java_workload_docstring}")
|
||||
elseif(WITH_JAVA)
|
||||
set(WITH_JAVA_WORKLOAD ON CACHE BOOL "${java_workload_docstring}")
|
||||
else()
|
||||
set(WITH_JAVA_WORKLOAD OFF CACHE BOOL "${java_workload_docstring}")
|
||||
endif()
|
||||
if(WITH_JAVA_WORKLOAD)
|
||||
list(APPEND FDBSERVER_SRCS workloads/JavaWorkload.actor.cpp)
|
||||
endif()
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/workloads)
|
||||
|
||||
add_flow_target(EXECUTABLE NAME fdbserver SRCS ${FDBSERVER_SRCS})
|
||||
|
@ -184,5 +196,12 @@ target_include_directories(fdbserver PRIVATE
|
|||
${CMAKE_CURRENT_BINARY_DIR}/workloads
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/workloads)
|
||||
target_link_libraries(fdbserver PRIVATE fdbclient)
|
||||
if(WITH_JAVA_WORKLOAD)
|
||||
if(NOT JNI_FOUND)
|
||||
message(SEND_ERROR "Trying to build Java workload but couldn't find JNI")
|
||||
endif()
|
||||
target_include_directories(fdbserver PRIVATE "${JNI_INCLUDE_DIRS}")
|
||||
target_link_libraries(fdbserver PRIVATE "${JNI_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
fdb_install(TARGETS fdbserver DESTINATION sbin COMPONENT server)
|
||||
|
|
|
@ -0,0 +1,511 @@
|
|||
#include "workloads.actor.h"
|
||||
#include <flow/ThreadHelper.actor.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <fdbrpc/simulator.h>
|
||||
#include <fdbclient/IClientApi.h>
|
||||
#include <fdbclient/ThreadSafeTransaction.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <flow/actorcompiler.h> // must be last include
|
||||
|
||||
extern void flushTraceFileVoid();
|
||||
|
||||
namespace {
|
||||
|
||||
void printTrace(JNIEnv* env, jobject self, jint severity, jstring message, jobject details) {
|
||||
jboolean isCopy;
|
||||
const char* msg = env->GetStringUTFChars(message, &isCopy);
|
||||
std::unordered_map<std::string, std::string> detailsMap;
|
||||
if (details != nullptr) {
|
||||
jclass mapClass = env->FindClass("java/util/Map");
|
||||
jclass setClass = env->FindClass("java/util/Set");
|
||||
jclass iteratorClass = env->FindClass("java/util/Iterator");
|
||||
jmethodID keySetID = env->GetMethodID(mapClass, "keySet", "()Ljava/util/Set;");
|
||||
jobject keySet = env->CallObjectMethod(details, keySetID);
|
||||
jmethodID iteratorMethodID = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
||||
jobject iterator = env->CallObjectMethod(keySet, iteratorMethodID);
|
||||
jmethodID hasNextID = env->GetMethodID(iteratorClass, "hasNext", "()Z");
|
||||
jmethodID nextID = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
|
||||
jmethodID getID = env->GetMethodID(mapClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
while (env->CallBooleanMethod(iterator, hasNextID)) {
|
||||
jobject next = env->CallObjectMethod(iterator, nextID);
|
||||
jstring key = jstring(next);
|
||||
jstring value = jstring(env->CallObjectMethod(details, getID, next));
|
||||
auto keyStr = env->GetStringUTFChars(key, nullptr);
|
||||
auto keyLen = env->GetStringUTFLength(key);
|
||||
auto valueStr = env->GetStringUTFChars(value, nullptr);
|
||||
auto valueLen = env->GetStringUTFLength(value);
|
||||
detailsMap.emplace(std::string(keyStr, keyLen), std::string(valueStr, valueLen));
|
||||
env->ReleaseStringUTFChars(key, keyStr);
|
||||
env->ReleaseStringUTFChars(value, valueStr);
|
||||
env->DeleteLocalRef(key);
|
||||
env->DeleteLocalRef(value);
|
||||
}
|
||||
}
|
||||
auto f = onMainThread([severity, &detailsMap, msg]() -> Future<Void> {
|
||||
TraceEvent evt(Severity(severity), msg);
|
||||
for (const auto& p : detailsMap) {
|
||||
evt.detail(p.first, p.second);
|
||||
}
|
||||
return Void();
|
||||
});
|
||||
f.blockUntilReady();
|
||||
if (isCopy) {
|
||||
env->ReleaseStringUTFChars(message, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void sendVoid(JNIEnv* env, jobject self, jlong promisePtr) {
|
||||
auto f = onMainThread([promisePtr]() -> Future<Void> {
|
||||
Promise<Void>* p = reinterpret_cast<Promise<Void>*>(promisePtr);
|
||||
p->send(Void());
|
||||
delete p;
|
||||
return Void();
|
||||
});
|
||||
}
|
||||
|
||||
void sendBool(JNIEnv* env, jobject self, jlong promisePtr, jboolean value) {
|
||||
auto f = onMainThread([promisePtr, value]() -> Future<Void> {
|
||||
Promise<bool>* p = reinterpret_cast<Promise<bool>*>(promisePtr);
|
||||
p->send(value);
|
||||
delete p;
|
||||
return Void();
|
||||
});
|
||||
}
|
||||
|
||||
void setProcessID(JNIEnv* env, jobject self, jlong processID) {
|
||||
if (g_network->isSimulated()) {
|
||||
g_simulator.currentProcess = reinterpret_cast<ISimulator::ProcessInfo*>(processID);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wwritable-strings"
|
||||
JNINativeMethod workloadMethods[] = { { "log", "(ILjava/lang/String;Ljava/util/Map;)V",
|
||||
reinterpret_cast<void*>(&printTrace) },
|
||||
{ "sendVoid", "(J)V", reinterpret_cast<void*>(&sendVoid) },
|
||||
{ "sendBool", "(JZ)V", reinterpret_cast<void*>(&sendBool) },
|
||||
{ "setProcessID", "(J)V", reinterpret_cast<void*>(&setProcessID) }
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
struct JVMContext {
|
||||
JNIEnv* env = nullptr;
|
||||
JavaVM* jvm = nullptr;
|
||||
// the JVM requires char* args
|
||||
std::vector<char*> jvmArgs;
|
||||
jclass workloadClass;
|
||||
jclass throwableClass;
|
||||
jclass fdbClass;
|
||||
jobject fdbObject;
|
||||
bool success = true;
|
||||
|
||||
template<class Args>
|
||||
JVMContext(Args&& jvmArgs)
|
||||
: jvmArgs(std::forward<Args>(jvmArgs))
|
||||
, fdbClass(nullptr)
|
||||
, fdbObject(nullptr) {
|
||||
init();
|
||||
}
|
||||
|
||||
~JVMContext() {
|
||||
TraceEvent(SevDebug, "JVMContextDestruct");
|
||||
flushTraceFileVoid();
|
||||
if (jvm) {
|
||||
if (fdbObject) {
|
||||
env->DeleteGlobalRef(fdbObject);
|
||||
}
|
||||
jvm->DestroyJavaVM();
|
||||
}
|
||||
for (auto& arr : jvmArgs) {
|
||||
delete[] arr;
|
||||
}
|
||||
TraceEvent(SevDebug, "JVMContextDestructDone");
|
||||
flushTraceFileVoid();
|
||||
}
|
||||
|
||||
bool checkException() {
|
||||
auto flag = env->ExceptionCheck();
|
||||
if (flag) {
|
||||
jthrowable exception = env->ExceptionOccurred();
|
||||
TraceEvent(SevError, "JavaException");
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void initializeFDB() {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
fdbClass = env->FindClass("com/apple/foundationdb/FDB");
|
||||
jmethodID selectMethod = env->GetStaticMethodID(fdbClass, "selectAPIVersion", "(IZ)Lcom/apple/foundationdb/FDB;");
|
||||
if (!checkException()) {
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
fdbObject = env->CallStaticObjectMethod(fdbClass, selectMethod, jint(610), jboolean(false));
|
||||
if (!checkException()) {
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
TraceEvent(SevDebug, "InitializeJVM");
|
||||
flushTraceFileVoid();
|
||||
JavaVMInitArgs args;
|
||||
args.version = JNI_VERSION_1_6;
|
||||
args.ignoreUnrecognized = JNI_TRUE;
|
||||
args.nOptions = jvmArgs.size();
|
||||
std::unique_ptr<JavaVMOption[]> options(new JavaVMOption[args.nOptions]);
|
||||
for (int i = 0; i < args.nOptions; ++i) {
|
||||
options[i].optionString = jvmArgs[i];
|
||||
TraceEvent(SevDebug, "AddJVMOption")
|
||||
.detail("Option", reinterpret_cast<const char*>(options[i].optionString));
|
||||
flushTraceFileVoid();
|
||||
}
|
||||
args.options = options.get();
|
||||
{
|
||||
TraceEvent evt(SevDebug, "StartVM");
|
||||
for (int i = 0; i < args.nOptions; ++i) {
|
||||
evt.detail(format("Option-%d", i), reinterpret_cast<const char*>(options[i].optionString));
|
||||
}
|
||||
}
|
||||
flushTraceFileVoid();
|
||||
auto res = JNI_CreateJavaVM(&jvm, reinterpret_cast<void**>(&env), &args);
|
||||
if (res == JNI_ERR) {
|
||||
success = false;
|
||||
env->ExceptionDescribe();
|
||||
return;
|
||||
}
|
||||
TraceEvent(SevDebug, "JVMStarted");
|
||||
flushTraceFileVoid();
|
||||
throwableClass = env->FindClass("java/lang/Throwable");
|
||||
workloadClass = env->FindClass("com/apple/foundationdb/testing/AbstractWorkload");
|
||||
if (workloadClass == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "ClassNotFound")
|
||||
.detail("ClassName", "com/apple/foundationdb/testing/AbstractWorkload");
|
||||
return;
|
||||
}
|
||||
TraceEvent(SevDebug, "RegisterNatives")
|
||||
.detail("ThrowableClass", format("%x", reinterpret_cast<uintptr_t>(throwableClass)))
|
||||
.detail("WorkloadClass", format("%x", reinterpret_cast<uintptr_t>(workloadClass)))
|
||||
.detail("NumMethods", sizeof(workloadMethods)/sizeof(workloadMethods[0]));
|
||||
flushTraceFileVoid();
|
||||
env->RegisterNatives(workloadClass, workloadMethods, sizeof(workloadMethods)/sizeof(workloadMethods[0]));
|
||||
success = checkException() && success;
|
||||
initializeFDB();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct JavaWorkload : TestWorkload {
|
||||
static const std::string name;
|
||||
// From https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html#unloading_the_vm
|
||||
// > Creation of multiple VMs in a single process is not supported.
|
||||
// This means, that we have to share the VM across workloads.
|
||||
static std::weak_ptr<JVMContext> globalVM;
|
||||
std::shared_ptr<JVMContext> vm;
|
||||
|
||||
std::string className;
|
||||
|
||||
bool success = true;
|
||||
jclass implClass;
|
||||
jobject impl = nullptr;
|
||||
|
||||
explicit JavaWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
|
||||
className = getOption(options, LiteralStringRef("workloadClass"), LiteralStringRef("")).toString();
|
||||
if (className == "") {
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
auto jvmOptions = getOption(options, LiteralStringRef("jvmOptions"), std::vector<std::string>{});
|
||||
vm = globalVM.lock();
|
||||
if (!vm) {
|
||||
std::vector<char*> args;
|
||||
args.reserve(jvmOptions.size());
|
||||
for (const auto& opt : jvmOptions) {
|
||||
char* option = new char[opt.size() + 1];
|
||||
option[opt.size()] = '\0';
|
||||
std::copy(opt.begin(), opt.end(), option);
|
||||
args.emplace_back(option);
|
||||
}
|
||||
vm = std::make_shared<JVMContext>(args);
|
||||
globalVM = vm;
|
||||
success = vm->success;
|
||||
} else {
|
||||
success = vm->success;
|
||||
}
|
||||
if (success) {
|
||||
try {
|
||||
createContext();
|
||||
} catch (Error& e) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "JavaContextCreationFailed")
|
||||
.error(e);
|
||||
}
|
||||
}
|
||||
TraceEvent(SevDebug, "JavaWorkloadConstructed")
|
||||
.detail("Success", success);
|
||||
flushTraceFileVoid();
|
||||
}
|
||||
|
||||
~JavaWorkload() {
|
||||
if (vm && impl) {
|
||||
try {
|
||||
auto shutdownID = getMethodID(vm->workloadClass, "shutdown", "()V");
|
||||
vm->env->CallVoidMethod(impl, shutdownID);
|
||||
if (!checkException()) {
|
||||
TraceEvent(SevError, "JavaWorkloadShutdownFailed")
|
||||
.detail("Reason", "AbstractWorkload::shutdown call");
|
||||
}
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevError, "JavaWorkloadShutdownFailed")
|
||||
.detail("Reason", "Exception");
|
||||
}
|
||||
}
|
||||
TraceEvent(SevDebug, "DestroyJavaWorkload");
|
||||
flushTraceFileVoid();
|
||||
if (vm && vm->env && impl)
|
||||
vm->env->DeleteGlobalRef(impl);
|
||||
TraceEvent(SevDebug, "DestroyJavaWorkloadComplete");
|
||||
flushTraceFileVoid();
|
||||
}
|
||||
|
||||
bool checkException() {
|
||||
return vm->checkException();
|
||||
}
|
||||
|
||||
void createContext() {
|
||||
std::transform(className.begin(), className.end(), className.begin(), [](char c) {
|
||||
if (c == '.') return '/';
|
||||
return c;
|
||||
});
|
||||
implClass = vm->env->FindClass(className.c_str());
|
||||
if (implClass == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "JavaWorkloadNotFound").detail("JavaClass", className);
|
||||
return;
|
||||
}
|
||||
if (!vm->env->IsAssignableFrom(implClass, vm->workloadClass)) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "JClassNotAWorkload").detail("Class", className);
|
||||
return;
|
||||
}
|
||||
jint initalCapacity = options.size() * 2;
|
||||
jclass hashMapCls = vm->env->FindClass("java/util/HashMap");
|
||||
if (hashMapCls == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "ClassNotFound")
|
||||
.detail("ClassName", "java/util/HashMap");
|
||||
return;
|
||||
}
|
||||
jmethodID put = vm->env->GetMethodID(hashMapCls, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
if (put == nullptr) {
|
||||
checkException();
|
||||
TraceEvent(SevError, "JavaMethodNotFound")
|
||||
.detail("Class", "java/util/HashMap")
|
||||
.detail("MethodName", "put")
|
||||
.detail("Signature", "(Ljava/lang/Object;Ljava/lang/Object)Ljava/lang/Object;");
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
jmethodID constr = vm->env->GetMethodID(hashMapCls, "<init>", "(I)V");
|
||||
jobject hashMap = vm->env->NewObject(hashMapCls, constr, initalCapacity);
|
||||
if (hashMap == nullptr || !checkException()) {
|
||||
TraceEvent(SevError, "JavaConstructionFailed")
|
||||
.detail("Class", "java/util/HashMap");
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
for (auto& kv : options) {
|
||||
auto key = vm->env->NewStringUTF(reinterpret_cast<const char*>(kv.key.begin()));
|
||||
auto value = vm->env->NewStringUTF(reinterpret_cast<const char*>(kv.value.begin()));
|
||||
vm->env->CallVoidMethod(hashMap, put, key, value);
|
||||
vm->env->DeleteLocalRef(key);
|
||||
vm->env->DeleteLocalRef(value);
|
||||
}
|
||||
auto workloadContextClass = findClass("com/apple/foundationdb/testing/WorkloadContext");
|
||||
auto workloadContextConstructor = getMethodID(workloadContextClass, "<init>", "(Ljava/util/Map;IIJJ)V");
|
||||
jlong processID = 0;
|
||||
if (g_network->isSimulated()) {
|
||||
processID = reinterpret_cast<jlong>(g_simulator.getCurrentProcess());
|
||||
}
|
||||
TraceEvent(SevDebug, "WorkloadContextConstructorFound")
|
||||
.detail("FieldID", format("%x", reinterpret_cast<uintptr_t>(workloadContextConstructor)));
|
||||
flushTraceFileVoid();
|
||||
auto workloadContext = vm->env->NewObject(workloadContextClass, workloadContextConstructor, hashMap,
|
||||
jint(clientId), jint(clientCount), jlong(sharedRandomNumber),
|
||||
processID);
|
||||
if (!checkException() || workloadContext == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "CouldNotCreateWorkloadContext");
|
||||
}
|
||||
TraceEvent(SevDebug, "WorkloadContextConstructed")
|
||||
.detail("Object", format("%x", reinterpret_cast<uintptr_t>(workloadContext)));
|
||||
flushTraceFileVoid();
|
||||
auto implConstr = vm->env->GetMethodID(implClass, "<init>", "(Lcom/apple/foundationdb/testing/WorkloadContext;)V");
|
||||
if (!checkException() || implConstr == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "JavaWorkloadNotDefaultConstructible").detail("Class", className);
|
||||
return;
|
||||
}
|
||||
impl = vm->env->NewObject(implClass, implConstr, workloadContext);
|
||||
if (!checkException() || impl == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "JavaWorkloadConstructionFailed").detail("Class", className);
|
||||
return;
|
||||
}
|
||||
vm->env->NewGlobalRef(impl);
|
||||
}
|
||||
|
||||
std::string description() override { return JavaWorkload::name; }
|
||||
|
||||
jclass findClass(const char* className) {
|
||||
jclass res = vm->env->FindClass(className);
|
||||
if (res == nullptr) {
|
||||
checkException();
|
||||
success = false;
|
||||
TraceEvent(SevError, "ClassNotFound")
|
||||
.detail("ClassName", className);
|
||||
throw internal_error();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
jmethodID getMethodID(jclass clazz, const char* name, const char* sig) {
|
||||
auto res = vm->env->GetMethodID(clazz, name, sig);
|
||||
if (!checkException() || res == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "JavaMethodNotFound")
|
||||
.detail("Name", name)
|
||||
.detail("Signature", sig);
|
||||
throw internal_error();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
jfieldID getStaticFieldID(jclass clazz, const char* name, const char* signature) {
|
||||
auto res = vm->env->GetStaticFieldID(clazz, name, signature);
|
||||
if (!checkException() || res == nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "FieldNotFound")
|
||||
.detail("FieldName", name)
|
||||
.detail("Signature", signature);
|
||||
throw internal_error();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
jobject getStaticObjectField(jclass clazz, jfieldID field) {
|
||||
auto res = vm->env->GetStaticObjectField(clazz, field);
|
||||
if (!checkException() || res != nullptr) {
|
||||
success = false;
|
||||
TraceEvent(SevError, "CouldNotGetStaticObjectField");
|
||||
throw operation_failed();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template<class Ret>
|
||||
Future<Ret> callJava(Database const& db, const char* method, Ret failed) {
|
||||
TraceEvent(SevDebug, "CallJava")
|
||||
.detail("Method", method);
|
||||
flushTraceFileVoid();
|
||||
try {
|
||||
auto cx = db.getPtr();
|
||||
cx->addref();
|
||||
// First we need an executor for the Database class
|
||||
jmethodID executorMethod = getMethodID(vm->workloadClass, "getExecutor", "()Ljava/util/concurrent/Executor;");
|
||||
jobject executor = vm->env->CallObjectMethod(impl, executorMethod);
|
||||
if (!checkException()) {
|
||||
success = false;
|
||||
return failed;
|
||||
}
|
||||
if (executor == nullptr) {
|
||||
TraceEvent(SevError, "JavaExecutorIsVoid");
|
||||
success = false;
|
||||
return failed;
|
||||
}
|
||||
Reference<IDatabase> database(new ThreadSafeDatabase(cx));
|
||||
jlong databasePtr = reinterpret_cast<jlong>(database.extractPtr());
|
||||
jclass databaseClass = findClass("com/apple/foundationdb/FDBDatabase");
|
||||
|
||||
// now we can create the Java Database object
|
||||
auto sig = "(JLjava/util/concurrent/Executor;)V";
|
||||
jmethodID databaseConstructor = getMethodID(databaseClass, "<init>", sig);
|
||||
jobject javaDatabase = vm->env->NewObject(databaseClass, databaseConstructor, databasePtr, executor);
|
||||
if (!checkException() || javaDatabase == nullptr) {
|
||||
TraceEvent(SevError, "ConstructingDatabaseFailed")
|
||||
.detail("ConstructirSignature", sig);
|
||||
success = false;
|
||||
return failed;
|
||||
}
|
||||
|
||||
auto p = new Promise<Ret>();
|
||||
jmethodID methodID = getMethodID(vm->workloadClass, method, "(Lcom/apple/foundationdb/Database;J)V");
|
||||
vm->env->CallVoidMethod(impl, methodID, javaDatabase, reinterpret_cast<jlong>(p));
|
||||
checkException();
|
||||
if (!checkException() || !success) {
|
||||
delete p;
|
||||
return failed;
|
||||
}
|
||||
return p->getFuture();
|
||||
// and now we can call the method with the created Database
|
||||
} catch (Error& e) {
|
||||
TraceEvent("CallJavaFailed")
|
||||
.error(e);
|
||||
success = false;
|
||||
return failed;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Void> setup(Database const& cx) override {
|
||||
if (!success) {
|
||||
return Void();
|
||||
}
|
||||
return callJava<Void>(cx, "setup", Void());
|
||||
}
|
||||
Future<Void> start(Database const& cx) override {
|
||||
if (!success) {
|
||||
return Void();
|
||||
}
|
||||
return callJava<Void>(cx, "start", Void());
|
||||
}
|
||||
Future<bool> check(Database const& cx) override {
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
return callJava<bool>(cx, "check", false);
|
||||
}
|
||||
void getMetrics(vector<PerfMetric>& m) override {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
virtual double getCheckTimeout() {
|
||||
if (!success) {
|
||||
return 3000;
|
||||
}
|
||||
jmethodID methodID = vm->env->GetMethodID(implClass, "getCheckTimeout", "()D");
|
||||
jdouble res = vm->env->CallDoubleMethod(impl, methodID);
|
||||
checkException();
|
||||
if (!success) {
|
||||
return 3000;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
const std::string JavaWorkload::name = "JavaWorkload";
|
||||
std::weak_ptr<JVMContext> JavaWorkload::globalVM;
|
||||
} // namespace
|
||||
|
||||
WorkloadFactory<JavaWorkload> JavaWorkloadFactory(JavaWorkload::name.c_str());
|
Loading…
Reference in New Issue