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

Reimplement GlobalRef #67

Merged
merged 5 commits into from Jan 10, 2018

Conversation

alexander-irbis
Copy link
Contributor

@alexander-irbis alexander-irbis commented Jan 8, 2018

Description of the problem: #66

This PR depends on invocation_api branch.

The current implementation assumes the need to maintain the previous implementation, but introduces breaking changes: old GlobalRef is renamed to AttachedGlobalRef, old module global_ref is renamed to global_ref_ad, and old JNIEnv::new_global_ref() is renamed to JNIEnv::new_global_ref_attached(). I have no answer so far whether it is necessary. It seems to me that it is better to completely remove AttachedGlobalRef.

The current JNIEnv implementation assumes only a hierarchical execution order, and this also imposes some restrictions on the new implementation of GlobalRef. But this issue can be solved separately in a subsequent PR and after discussion.


What other tests to add? Ideas are welcome.


export LD_LIBRARY_PATH=`java -XshowSettings:properties -version 2>&1 > /dev/null | grep 'java.home' | sed 's/^.*= //'`/lib/server/:$LD_LIBRARY_PATH

And then

cargo test --features="invocation"

or

cargo test --all-features

@alexander-irbis alexander-irbis changed the base branch from master to invocation_api January 8, 2018 14:35
/// Turns an object into a global ref. This has the benefit of removing the
/// lifetime bounds since it's guaranteed to not get GC'd by java. It
/// releases the GC pin upon being dropped.
pub fn new_global_ref(&self, obj: JObject) -> Result<GlobalRef> {
non_null!(obj, "new_global_ref obj argument");
let new_ref: JObject = jni_call!(self.internal, NewGlobalRef, obj.into_inner());
let global = unsafe { GlobalRef::new(self.internal, new_ref.into_inner()) };
let global = unsafe { GlobalRef::new(self.get_java_vm()?, new_ref.into_inner()) };
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now, a JNI Call is made here. But is it necessary? Perhaps in the future JNIEnv will just have a cached reference to JavaVM

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 assume that an underlying JNIEnv has a pointer to JavaVM, therefore, the cost is likely to be negligible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In practice, this call is executed one and a half times longer than creating + deleting a copy of Arc. We can really neglect it for now.

test tests::jni_get_java_vm                      ... bench:       1,789 ns/iter (+/- 431)
test tests::native_arc                           ... bench:       1,138 ns/iter (+/- 269)

/// Turns an object into a global ref. This has the benefit of removing the
/// lifetime bounds since it's guaranteed to not get GC'd by java. It
/// releases the GC pin upon being dropped.
pub fn new_global_ref(&self, obj: JObject) -> Result<GlobalRef> {
non_null!(obj, "new_global_ref obj argument");
let new_ref: JObject = jni_call!(self.internal, NewGlobalRef, obj.into_inner());
let global = unsafe { GlobalRef::new(self.internal, new_ref.into_inner()) };
let global = unsafe { GlobalRef::new(self.get_java_vm()?, new_ref.into_inner()) };
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 assume that an underlying JNIEnv has a pointer to JavaVM, therefore, the cost is likely to be negligible.

pub unsafe fn new(env: *mut sys::JNIEnv, obj: jobject) -> Self {
/// Creates a new global reference. This assumes that `NewGlobalRef`
/// has already been called.
pub unsafe fn new(vm: JavaVM, obj: sys::jobject) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

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

Am I right to assume this method is not supposed to be used by the library clients? If so, is it possible in Rust to make it available only inside the library (= hide from the users)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with this, we can make such methods visible only for this crate itself.

barrier.wait();
for _ in 0..10000 {
unwrap(&env, unwrap(&env, env.call_method(
atomic_integer.as_obj(), "addAndGet", "(I)I", &[JValue::from(-1)])).i());
Copy link
Contributor

Choose a reason for hiding this comment

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

decrementAndGet here, and incrementAndGet below?


let global_ref_1 = unwrap(&env, env.new_global_ref_attached(local_ref.as_obj()));

{
Copy link
Contributor

Choose a reason for hiding this comment

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

Since there are multiple asserts and conversions between various reference types, I'd add comments clarifying what you are trying to test, e.g.

// Test several global refs to the same object work
…
// Test detached & re-attached global ref works
…
// Test the first global ref unaffected by another gr to the same object detached

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seemed to me that this was too obvious, but ok, let it be

}

#[test]
pub fn global_ref_works_in_other_threads() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd simplify the test to make it easier to understand by reviewers & future maintainers:

  1. Use incrementAndGet in both threads.
  2. Extract a magic 10000 in a constant (e.g., incrementsInEachThread, or numIncrements).
  3. Use the same constant in both loops (no 10000 + 1, because that is likely to confuse readers).
  4. assert that the resulting value is equal to the number of threads (2) multiplied by the number of increments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Everything went to this, but I somehow forgot to finish it.

@dmitry-timofeev
Copy link
Contributor

I wonder what is the deprecation policy:

  1. Shall the old GlobalRef simply go away?
  2. Shall it be deprecated first with a #[deprecated(since = "x.y.z", note="Use NewGlobalRef")] and retain its original name so that the existing users are not affected, and then be turned into NewGlobalRef (old GlobalRef removed + NewGlobalRef renamed into GlobalRef)?
  3. Shall it be deprecated but get a new name (like in this PR)?

@jechase, @alexander-irbis, @DarkEld3r , @fpoli, what do you think? I think the (2) is the most user-friendly, but requires a little bit more work, the (1) is the easiest.

Restricted the visibility of `GlobalRef::new()`.
Updated tests.
Added comments.
Copy link
Contributor

@dmitry-timofeev dmitry-timofeev left a comment

Choose a reason for hiding this comment

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

LGTM.

Regarding some extra tests, I can think of tests verifying that drop works as expected:

  1. When the current native thread is attached, it remains attached after GlobalRef#drop finishes, and other references to the same Java object (e.g., local or system) are not usable. AFAIK, that will require little hacks to suppress lifetime checker and make JObject outlive GlobalRef.
  2. When the current native thread is detached, it remains detached, the reference becomes unusable.

let res = match self.vm.get_env() {
Ok(env) => drop_impl(&env, self.as_obj()),
Err(_) => self.vm
.attach_current_thread()
Copy link
Contributor

Choose a reason for hiding this comment

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

Please consider #69

}
Err(Error(ErrorKind::ThreadDetached, _)) => {
let env = self.vm.attach_current_thread()?;
let _ = self.attach_impl(&env);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please consider #69

@fpoli
Copy link
Contributor

fpoli commented Jan 9, 2018

I wonder what is the deprecation policy:

  1. Shall the old GlobalRef simply go away?
  2. Shall it be deprecated first with a #[deprecated(since = "x.y.z", note="Use NewGlobalRef")] and retain its original name so that the existing users are not affected, and then be turned into NewGlobalRef (old GlobalRef removed + NewGlobalRef renamed into GlobalRef)?
  3. Shall it be deprecated but get a new name (like in this PR)?

@jechase, @alexander-irbis, @DarkEld3r , @fpoli, what do you think? I think the (2) is the most user-friendly, but requires a little bit more work, the (1) is the easiest.

I agree. In my opinion it's ok to just go with (1) and a (major?) version change.

@alexander-irbis
Copy link
Contributor Author

alexander-irbis commented Jan 9, 2018

When the current native thread is detached, it remains detached, the reference becomes unusable.

@dmitry-timofeev currently, references unusable without JNIEnv object. This object keeps a thread attachment and provides access to JNI calls. So, it's not possible to use references without an attachment. In other words: it is tested by a type system.

@dmitry-timofeev
Copy link
Contributor

… In other words: it is tested by a type system.

You are right, the second test doesn't make much sense with the second reference being a local one.

@@ -36,6 +36,10 @@ pub use self::jbytebuffer::*;
mod global_ref;
pub use self::global_ref::*;

// For when you want to store a reference to a java object
Copy link
Member

Choose a reason for hiding this comment

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

"For storing a reference to a java object" if you want the same comment style as below. I'm not sure if it is needed at all.

let res = match self.vm.get_env() {
Ok(env) => drop_impl(&env, self.as_obj()),
Err(_) => {
warn!("`GlobalRef` attaches JNI-thread while dropping its instance.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this message is for (potentially) ignorant users, I'd be more verbose and suggest what they shall do:
Dropping a GlobalRef in a detached thread. Fix your code if this message appears frequently (see the GlobalRef docs)

Currently it's unclear from the message whether it shall concern a user at all.

/// since it requires a pointer to the `JNIEnv` to do anything useful with it.
/// outlive the `JNIEnv` that it came from and can be used in other threads.
///
/// Note that the native thread must be `attach`ed to the jni thread (this is so when you have
Copy link
Contributor

Choose a reason for hiding this comment

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

It is _recommended_ that a native thread that drops the global reference is attached to the Java thread (i.e., has an instance of `JNIEnv`). If the native thread is *not* attached, the `GlobalRef#drop` will print a warning and implicitly `attach` and `detach` it, which significantly affects performance.
?

@jrobsonchase
Copy link
Contributor

I'm good with removing the old GlobalRef and making a new semver-incompatible version. It was going to need to happen for the invocation api branch anyway.

@jrobsonchase
Copy link
Contributor

If this is ready to go in, I can go ahead and merge it and the invocation api branch, assuming there's nothing major that I've overlooked.

@jrobsonchase jrobsonchase merged commit 95214d5 into jni-rs:invocation_api Jan 10, 2018
@alexander-irbis alexander-irbis changed the title [WIP] Reimplement GlobalRef Reimplement GlobalRef Jan 10, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants