Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor AbstractEventListener to cache Class instance in JNI code #12844

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

rhubner
Copy link
Contributor

@rhubner rhubner commented Jul 8, 2024

Problem:

NoClassDefFoundError thrown from JNI code when EventListener is setup.

Condition to replicate :

RocksDB jar must be loaded with a custom class loader, for example Web container, Spring boot, …

Cause:

Limitation of java class loader in JNI API. JNI code is able to load classes from the same custom class loader only when the call to JNI was initiated from Java and from class loaded with the same class loader. Unfortunately in the callback, the call is initiated by RocksDB and then we attach to the existing JVM. In this situation, limitation of FindClass method takes place and JVM is able to only load classes which are specified on start of JVM.

Solution:

To overcome this limitation, in EventListenerJniCallback constructor JNI code, we load all instances of java.lang.Class required later by the callback and make them global references. Because in this phase, JNI code is called from Java, JVM knows where to look for these classes. Then later we are able to create instances of these classes, even when the JNI code wasn’t initiated from Java code and the current thread was attached to JVM.

This PR fix #10686

Copy link
Contributor

@alanpaxton alanpaxton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've picked up some bits and pieces that should be fixed.

* This exception should be used in situation when critical
* JNI call failed. For example GetJvm, FindClass, ...
*/
class JniException : public std::exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a perfect world KVException would probably inherit from JniException - templates may be involved also :-( We should add a comment to link the two. And even a backlog ticket for the work ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created card in backlog (3308)

* It loads rocksDB code with custom classloader and then test that all
* event data for event listener can be instantiated.
*/
public class EventListenerClassloaderTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has a bunch of check warnings when I open it in IntelliJ. I'm surprised checkbugs isn't complaining. Is it missing from a list ?

Copy link
Contributor Author

@rhubner rhubner Jul 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think checkbugs verify only production code, not tests.
I address most of the IntelliJ warning. Including wrong order on :
assertThat(jarPath).as("Java property 'rocks-jar' was not setup properly").isNotNull();

+ "classLoader"
+ " to verify that callback cache all class instances.");
} catch (ClassNotFoundException e) {
;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty catch

Copy link
Contributor Author

@rhubner rhubner Jul 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's empty for purpose. We are testing if RocksDB is on class path. And success state is ClassNotFoundException. Not on default classpath. 🤔 Maybe if we can use somehow to translate this to assumptions org.junit.Assume 🤔.

}

String jarPath = System.getProperty("rocks-jar");
assertThat(jarPath).isNotNull().as("Java property 'rocks-jar' was not setup properly");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result of as ignored - this assertThat isn't doing what you think it is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. as must be exactly after assertThat

assertThat(jarPath).isNotNull().as("Java property 'rocks-jar' was not setup properly");

Path classesDir = Paths.get(jarPath);
ClassLoader cl = new URLClassLoader(new URL[] {classesDir.toAbsolutePath().toUri().toURL()});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URLClassLoader is Closeable - try with resources needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


Class testableEventListenerClazz = cl.loadClass("org.rocksdb.test.TestableEventListener");
Method invokeAllCallbacksInThread =
testableEventListenerClazz.getMethod("invokeAllCallbacksInThread");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchecked call getMethod

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem It will fail whole test with nice exception and stack trace. I think better that manual check.


// StatusJni can't cache jclass because in descructor we need JniUtil,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spelling - destructor

// StatusJni can't cache jclass because in descructor we need JniUtil,
// JniUtil need RocksDBExceptionJni, and RocksDBExceptionJni need StatusJni.
// So I can't even change the order of the classes in portal.h
static jobject construct(JNIEnv* env, const Status& status, jclass jclazz,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you comment the method ? And explain the dependencies a bit more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add proper documentation.

auto status = env->GetJavaVM(vm);
if (status != 0) {
ROCKSDB_NAMESPACE::JniException::ThrowNew(
env, "Unable to retrieve instance ov JavaVM");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "instance of JavaVM"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

env, jobj, enabled_event_callbacks));
return GET_CPLUSPLUS_POINTER(sptr_event_listener);
} catch (ROCKSDB_NAMESPACE::JniException&) {
// We always throw JniException with Java Exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say:
// JNIException indicates that a Java Exception has been thrown in the env

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@alanpaxton
Copy link
Contributor

LGTM now. Happy for you to un-draft it.

@rhubner rhubner marked this pull request as ready for review August 1, 2024 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SIGSEGV calling EventListener's onCompactionBegin() callback method with rocksdbjni 7.4.5
3 participants