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:
mpilman 2019-03-31 17:57:43 -07:00
parent c532a00f30
commit e23e63c6ac
7 changed files with 727 additions and 8 deletions

View File

@ -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()

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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:

View File

@ -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)

View File

@ -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());