Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Java.Interop] Add JniPeerRegistrationScope?
Fixes: dotnet#4 Context: dotnet#426 Alternate names? * JavaPeerableCleanupPool * JavaPeerCleanup * JavaReferenceCleanup Issue dotnet#426 is an idea to implement a *non*-GC-Bridged `JniRuntime.JniValueManager` type, primarily for use with .NET Core. This was begun in a666a6f. What's missing is the answer to a question: what do do about `JniRuntime.JniValueManager.CollectPeers()`? With a Mono-style GC bridge, `CollectPeers()` is `GC.Collect()`. In a666a6f with .NET Core, `CollectPeers()` calls `IJavaPeerable.Dispose()` on all registered instances, which is "extreme". @jonpryor thought that if there were a *scope-based* way to selectively control which instances were disposed, that might be "better" and more understandable. Plus, this is issue dotnet#4! Add `JniPeerRegistrationScope`, which introduces a scope-based mechanism to control when `IJavaPeerable` instances are cleaned up: public enum JniPeerRegistrationScopeCleanup { RegisterWithManager, Dispose, Release, } public ref struct JniPeerRegistrationScope { public JniPeerRegistrationScope(JniPeerRegistrationScopeCleanup cleanup); public void Dispose(); } `JniPeerRegistrationScope` is a [`ref struct`][0], which means it can only be allocated on the runtime stack, ensuring that cleanup semantics are *scope* semantics. TODO: is that actually a good idea? If a `JniPeerRegistrationScope` is created using `JniPeerRegistrationScopeCleanup.RegisterWithManager`, existing behavior is followed. This is useful for nested scopes, should instances need to be registered with `JniRuntime.ValueManager`. If a `JniPeerRegistrationScope` is created using `.Dispose` or `.Release`, then: 1. Object references created within the scope are "thread-local"; they can be *used* by other threads, but `JniRuntime.JniValueManager.PeekPeer()` won't find existing values. 2. At the end of a `using` block / when `JniPeerRegistrationScope.Dispose()` is called, all collected instances will be `Dispose()`d (with `.Dispose`) or released (with `.Release`), and left to the GC to eventually finalize. For example: using (new JniPeerRegistrationScope (JniPeerRegistrationScopeCleanup.Dispose)) { var singleton = JavaSingleton.Singleton; // use singleton } // `singleton.Dispose()` is called at the end of the `using` block TODO: docs? TODO: *nested* scopes, and "bound" vs. "unbound" instance construction around `JniValueManager.GetValue<T>()` or `.CreateValue<T>()`, and *why* they should be treated differently. TODO: Should `CreateValue<T>()` be *removed*? name implies it always "creates" a new value, but implementation will return existing instances, so `GetValue<T>()` alone may be better. One related difference is that` `CreateValue<T>()` uses `PeekBoxedObject()`, while `GetValue<T>()` doesn't. *Should* it? [0]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#ref-struct
- Loading branch information