-
Notifications
You must be signed in to change notification settings - Fork 154
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
Reimplement GlobalRef
#67
Conversation
/// 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()) }; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) }; |
There was a problem hiding this comment.
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.
src/wrapper/objects/global_ref.rs
Outdated
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 { |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
tests/jni_global_refs.rs
Outdated
barrier.wait(); | ||
for _ in 0..10000 { | ||
unwrap(&env, unwrap(&env, env.call_method( | ||
atomic_integer.as_obj(), "addAndGet", "(I)I", &[JValue::from(-1)])).i()); |
There was a problem hiding this comment.
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?
tests/jni_global_refs.rs
Outdated
|
||
let global_ref_1 = unwrap(&env, env.new_global_ref_attached(local_ref.as_obj())); | ||
|
||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there are multiple assert
s 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
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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:
- Use
incrementAndGet
in both threads. - Extract a magic
10000
in a constant (e.g.,incrementsInEachThread
, ornumIncrements
). - Use the same constant in both loops (no
10000 + 1
, because that is likely to confuse readers). - assert that the resulting value is equal to the number of threads (2) multiplied by the number of increments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Everything went to this, but I somehow forgot to finish it.
I wonder what is the deprecation policy:
@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.
There was a problem hiding this 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:
- 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. - When the current native thread is detached, it remains detached, the reference becomes unusable.
src/wrapper/objects/global_ref.rs
Outdated
let res = match self.vm.get_env() { | ||
Ok(env) => drop_impl(&env, self.as_obj()), | ||
Err(_) => self.vm | ||
.attach_current_thread() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider #69
src/wrapper/objects/global_ref_ad.rs
Outdated
} | ||
Err(Error(ErrorKind::ThreadDetached, _)) => { | ||
let env = self.vm.attach_current_thread()?; | ||
let _ = self.attach_impl(&env); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider #69
I agree. In my opinion it's ok to just go with (1) and a (major?) version change. |
@dmitry-timofeev currently, references unusable without |
You are right, the second test doesn't make much sense with the second reference being a local one. |
src/wrapper/objects/mod.rs
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
src/wrapper/objects/global_ref.rs
Outdated
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."); |
There was a problem hiding this comment.
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.
src/wrapper/objects/global_ref.rs
Outdated
/// 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 |
There was a problem hiding this comment.
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.
?
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. |
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. |
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 toAttachedGlobalRef
, old moduleglobal_ref
is renamed toglobal_ref_ad
, and oldJNIEnv::new_global_ref()
is renamed toJNIEnv::new_global_ref_attached()
. I have no answer so far whether it is necessary. It seems to me that it is better to completely removeAttachedGlobalRef
.The current
JNIEnv
implementation assumes only a hierarchical execution order, and this also imposes some restrictions on the new implementation ofGlobalRef
. But this issue can be solved separately in a subsequent PR and after discussion.What other tests to add? Ideas are welcome.
And then
or