-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[core, ios, macos, android] Support non-copyable types with unique_any #10459
[core, ios, macos, android] Support non-copyable types with unique_any #10459
Conversation
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.
This looks more complicated than it needs to be, particularly the use of shared_ptr
/ enabled_shared_from_this
. I suggest the following scheme:
Not added to a style
No changes to the existing semantics:
- Java peer has a raw, owning pointer to C++ peer.
- C++ peer has a
unique_ptr
to C++ core source.
Avoid the need for the C++ peer to have a weak reference to the Java peer as follows:
- The only place the weak reference is needed is in
CustomGeometrySource::addToMap
. IfaddToMap
receives the Java peer as an argument, it doesn't need the weak reference. - So change
NativeMapView::addSource
to receive ajni::Object<Source>
Java peer, rather than rawSource*
asjni::jlong
. Obtain the C++ peer from the Java peer in the usual way, then pass the Java peer as an additional argument toaddToMap
.
Avoid storing anything in the any
field of the C++ core object at this stage. That isn't needed because there will be no calls to the callbacks of a source that's not added to a style.
Added to a style
Preserve the existing semantics:
- Java peer has a raw pointer to C++ peer.
- C++ peer has a reference to the C++ core source, but transfers ownership of the
unique_ptr
to the core style.
Add the following additional semantics:
- The C++ peer gets a global reference to the Java peer (as in this PR currently).
- Use the
any
field in the C++ core style to store aunique_ptr
to the C++ peer. The core style now owns core source, and the core source owns the C++ peer. The Java peer still has a raw pointer to the C++ peer, but it is now considered to be non-owning.unique_ptr
suffices to capture this ownership structure;shared_ptr
isn't necessary.
This seems fairly simple to reason about. When a source is not in a style, ownership points one way:
Java peer → C++ peer → core source
When a source is in a style, it points the other way:
Java peer ← C++ peer ← core source ← core style
No shared ownership is needed.
Note that you do need to handle the case where a source is removed from the style, and re-reverse the ownership in that case. That doesn't appear to be included in this PR currently.
@@ -17,11 +16,17 @@ | |||
|
|||
namespace mbgl { | |||
namespace android { | |||
struct SourceWrapper { |
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.
SourceWrapper
isn't necessary, you can store a std::shared_ptr<CustomGeometrySource>
(or unique_ptr
as I'm suggesting) directly in the any
.
jni::Object<> options) { | ||
auto sharedSource = std::make_shared<CustomGeometrySource>(env, javaSource, sourceId, options); | ||
javaSource.Set(env, nativePtrField, reinterpret_cast<jni::jlong>(sharedSource.get())); | ||
sharedSource->source.peer = SourceWrapper { sharedSource->shared_from_this() }; |
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.
You don't need shared_from_this
here -- you can just assign sharedSource->source.peer = sharedSource
.
auto finalize = [](jni::JNIEnv& env, jni::Object<CustomGeometrySource> javaSource) { | ||
auto sharedSource = std::shared_ptr<CustomGeometrySource>(reinterpret_cast<CustomGeometrySource *>((jlong)javaSource.Get(env, nativePtrField))); | ||
if (sharedSource) javaSource.Set(env, nativePtrField, (jlong)0); | ||
sharedSource.reset(); |
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.
This resets only the local instance of shared_ptr
, not the one in the C++ core peer
field, so both the C++ peer and the C++ core object will leak when a CustomGeometrySource
that's not added to a map is finalized.
}; | ||
|
||
auto finalize = [](jni::JNIEnv& env, jni::Object<CustomGeometrySource> javaSource) { | ||
auto sharedSource = std::shared_ptr<CustomGeometrySource>(reinterpret_cast<CustomGeometrySource *>((jlong)javaSource.Get(env, nativePtrField))); |
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.
Register "finalize" with the RegisterNativePeer
call so that you don't need to manually lookup the field and cast.
ef61709
to
abd6289
Compare
Instead of trying to hack around the copy-constructability requirement of I also pushed these changes down to The ownership changes have been implemented with
In addition, when a source is removed from the map, the original ownership chain is restored. |
abd6289
to
956e013
Compare
platform/darwin/src/MGLStyle.mm
Outdated
@@ -221,7 +221,7 @@ - (MGLSource *)sourceWithIdentifier:(NSString *)identifier | |||
} | |||
|
|||
- (MGLSource *)sourceFromMBGLSource:(mbgl::style::Source *)rawSource { | |||
if (MGLSource *source = rawSource->peer.empty() ? nil : mbgl::any_cast<SourceWrapper>(rawSource->peer).source) { | |||
if (MGLSource *source = rawSource->peer.has_value() ? mbgl::util::any_cast<SourceWrapper>(&(rawSource->peer))->source : nil) { |
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.
Can this continue to use the throwing version of any_cast
rather than the null-returning version, so that if it fails it's not undefined behavior?
@@ -20,6 +20,11 @@ namespace { | |||
|
|||
Source* initializeSourcePeer(mbgl::style::Source& coreSource) { | |||
Source* source; | |||
if (coreSource.peer.has_value()) { | |||
source = mbgl::util::any_cast<std::unique_ptr<Source>>(&coreSource.peer)->get(); | |||
return source; |
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.
Nit: follow the existing convention here -- change the subsequent if
to else if
and omit the early return -- or convert all the branches to early return and eliminate the source
temporary variably entirely.
@@ -68,6 +107,14 @@ namespace android { | |||
return std::move(ownedSource); | |||
} | |||
|
|||
jni::jobject* Source::getJavaPeer(jni::JNIEnv& 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.
Is this called anywhere?
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.
That's a 🐛 . It should've been called in createJavaSourcePeer()
0d157be
to
379baa3
Compare
6ec1ec3
to
7d28ab7
Compare
379baa3
to
9e70ea0
Compare
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.
Looks good, minor nits.
I wonder if we should let the c++ peer hold onto the (weak) reference of the Java peer eventually instead of passing it along in the native mapview methods. But that can be taken care of lateron.
@@ -858,7 +858,6 @@ jni::Array<jni::Object<Source>> NativeMapView::getSources(JNIEnv& env) { | |||
for (auto source : sources) { | |||
auto jSource = jni::Object<Source>(createJavaSourcePeer(env, *rendererFrontend, *source)); | |||
jSources.Set(env, index, jSource); | |||
jni::DeleteLocalRef(env, jSource); |
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.
Should this actually be removed? jSource
is still a local reference here right? In practice there will not be that many sources, but this could potentially overflow the local reference table I think.
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.
This is now a pointer to the global reference from GetJavaPeer
.
} | ||
|
||
void Source::removeFromMap(JNIEnv&, jni::Object<Source>, mbgl::Map& map) { | ||
//Cannot remove if not attached yet |
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.
space
@@ -68,6 +112,16 @@ namespace android { | |||
return std::move(ownedSource); | |||
} | |||
|
|||
jni::jobject* Source::getJavaPeer(jni::JNIEnv& env) { | |||
if(!javaPeer) { |
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.
space
// while also resetting it's nativePtr to 0 to prevent the subsequent GC of the Java peer from | ||
// re-entering this dtor. | ||
if (ownedSource.get() == nullptr && javaPeer.get() != nullptr) { | ||
//Manually clear the java peer |
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.
space
platform/darwin/src/MGLStyle.mm
Outdated
@@ -221,7 +221,7 @@ - (MGLSource *)sourceWithIdentifier:(NSString *)identifier | |||
} | |||
|
|||
- (MGLSource *)sourceFromMBGLSource:(mbgl::style::Source *)rawSource { | |||
if (MGLSource *source = rawSource->peer.empty() ? nil : mbgl::any_cast<SourceWrapper>(rawSource->peer).source) { | |||
if (MGLSource *source = rawSource->peer.has_value() ? mbgl::util::any_cast<SourceWrapper>(rawSource->peer).source : nil) { |
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.
extra space after ?
peerSource.release(); | ||
return result; | ||
} | ||
|
||
jni::jobject* removeSourceFromMap(std::unique_ptr<mbgl::style::Source>&& coreSource) { |
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.
This method is confusing. Why is it named removeSourceFromMap
, when it doesn't do that? Why does it construct a unique pointer and then immediate release it?
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.
unique_ptr
usage is not necessary and can be removed from both this function and createJavaSourcePeer
.
@@ -43,11 +45,16 @@ namespace android { | |||
jni::jobject* createJavaSourcePeer(jni::JNIEnv& env, AndroidRendererFrontend& frontend, mbgl::style::Source& coreSource) { | |||
std::unique_ptr<Source> peerSource = std::unique_ptr<Source>(initializeSourcePeer(coreSource)); |
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.
This doesn't look right. Why construct a unique pointer from a raw pointer, which might be owned by some other unique_ptr
, and then release it later?
@@ -20,7 +20,9 @@ namespace { | |||
|
|||
Source* initializeSourcePeer(mbgl::style::Source& coreSource) { | |||
Source* source; | |||
if (coreSource.is<mbgl::style::VectorSource>()) { | |||
if (coreSource.peer.has_value()) { |
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 think this case should be checked by createJavaSourcePeer
, not by initializeSourcePeer
. The return type of initializeSourcePeer
should be std::unique_ptr<Source>
.
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.
initializeSourcePeer
isn't just an initializer anymore. I think it can be changed to getSourcePeer
and skip the unique_ptr<>
since it's not really needed in createJavaSourcePeer
or removeSourceFromMap
4399576
to
75b84ae
Compare
@@ -856,9 +856,8 @@ jni::Array<jni::Object<Source>> NativeMapView::getSources(JNIEnv& env) { | |||
jni::Array<jni::Object<Source>> jSources = jni::Array<jni::Object<Source>>::New(env, sources.size(), Source::javaClass); | |||
int index = 0; | |||
for (auto source : sources) { | |||
auto jSource = jni::Object<Source>(createJavaSourcePeer(env, *rendererFrontend, *source)); | |||
auto jSource = jni::Object<Source>(Source::peerForCoreSource(env, *source, *rendererFrontend)); |
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.
Eliminate this temporary variable.
@@ -874,38 +873,25 @@ jni::Object<Source> NativeMapView::getSource(JNIEnv& env, jni::String sourceId) | |||
} | |||
|
|||
// Create and return the source's native peer | |||
return jni::Object<Source>(createJavaSourcePeer(env, *rendererFrontend, *coreSource)); | |||
return jni::Object<Source>(Source::peerForCoreSource(env, *coreSource, *rendererFrontend)); |
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.
jni::Object<Source>
isn't necessary.
*/ | ||
Source(mbgl::style::Source&); | ||
Source(jni::JNIEnv&, mbgl::style::Source&, jni::jobject*, AndroidRendererFrontend&); |
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.
jni::jobject*
→ jni::Object<Source>
// Remove the source from the map and take ownership | ||
ownedSource = map.getStyle().removeSource(source.getID()); | ||
|
||
// The source may not be removed if any layers still reference it |
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.
Good catch.
75b84ae
to
ccc57ae
Compare
Addresses @ivovandongen 's comment in #9983.
The problem highlighted is that the C++ Peer (
mbgl::android::CustomGeometrySource
) keeps a strong reference to the Java peer, creating a reference loop and preventing it from being garbage collected.The solution is to inverse the ownership relationship between the Java Peer (JP), C++ Peer(CP), and core source (CS) after it is added to the map.
Prior to this change:
JP owns CP (through
jni::registerNativePeer
helpers)CP owns CS (through
ownedSource
unique_ptr)JP owns CP (remains unchanged)
Core style owns CS (
ownedSource
is released)After this change:
JP has a shared pointer to CP (through
shared_ptr
managed byinitialize
andfinalize
lambdas)CP has a weak global reference to JP
CP owns CS (through
ownedSource
unique_ptr)CS has a shared pointer to CP (through the
any peer
field)CP owns JP (through a strong global reference)
CS owns CP (through the
shared_ptr
in thepeer
field)Core style owns the CS
CusotmGeometrySource
needs a global reference to the java peer to call out into the JVM when thefetchTile
andcancelTile
callbacks are invoked in core. These methods are invoked on the render thread, while the java peer reference was originally created on the main thread.cc @ivovandongen @jfirebaugh