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

Allow [Threadsafe] interfaces access to Arc<Self> #417

Closed
mhammond opened this issue Mar 16, 2021 · 9 comments
Closed

Allow [Threadsafe] interfaces access to Arc<Self> #417

mhammond opened this issue Mar 16, 2021 · 9 comments

Comments

@mhammond
Copy link
Member

mhammond commented Mar 16, 2021

We've a use-case where it would be ideal to have access to a clone of the Arc<Self> stored in the ArcHandleMap.

The motivation here is for an app-services component to register with the "sync manager". Without going into too much detail about the "spelling", think something like:

[Threadsafe]
interface Foo {
  [SomethingSomething]
  void register_with_something_external();
}

makes sense, where we call something with the signature:

fn register_with_something_external(self: Arc<Self>) { ... }

which leverages "method receivers". The intent is that we can avoid something gross like an <Arc<Arc<T>> and instead leverage the fact that uniffi already manages the Arc we need.

I'm opening this as a place-holder issue, trying to avoid too much bikeshedding at this stage (hence [SomethingSomething] :) - but I welcome it in general.

cc @rfk @lougeniaC64 @jhugman @dmose

┆Issue is synchronized with this Jira Task

mhammond added a commit to mhammond/uniffi-rs that referenced this issue Mar 16, 2021
…ch takes an `Arc::clone(self)`

Intent is a new attribute is added to such methods (and the method must be on
a `[ThreadSafe]` interface). I've chosen `[SomethingSomethingArc]` as the
attribute name :)

I attempted to change things so handle_map.rs no longer does the
ref-deref `&*` magic but instead delegates that to the macros, which
only does it if the new attribute exists. This *nearly* works, but
has unintended consequences I don't yet understand (ie, the
non-threadsafe generated code is using `&*obj` which makes no sense
at this hour).
@rfk
Copy link
Collaborator

rfk commented Mar 16, 2021

The intent is that we can avoid something gross like an <Arc<Arc> and instead leverage the fact
that uniffi already manages the Arc we need.

I haven't thought about this very deeply, but high-level, I'm concerned about leaking an incidental implementation of uniffi in this way. We happen to currently use an Arc<T> and a handlemap for [Threadsafe] interfaces, but we might want to try changing to something like a Box<T> in future for performance reasons.

Again without thinking very deeply, I wonder if it would be possible to invert the responsibility here, and have a way for you to declare that your interface is already inside an Arc<T> and you don't need uniffi to add a second one on for you. That would solve the double-arc problem, but I guess it wouldn't solve the "have to manually wrap your object in an Arc rather than having uniffi do it for you" problem...

@mhammond
Copy link
Member Author

mhammond commented Mar 16, 2021

I'm concerned about leaking an incidental implementation of uniffi in this way.

Yes, good point. Off the top of my head, maybe unifying (ha!) what constructors return and what I'm trying to achieve here - ie, defining what Self is? That might be somewhat orthogonal to ThreadSafe but might scale better...

@rfk
Copy link
Collaborator

rfk commented Mar 16, 2021

I'm concerned about leaking an incidental implementation of uniffi in this way.

I should also say, I'm not prepared to rule out such leakage entirely - for example in the hand-written bindings for places etc I believe we have some sort of getHandle() method that deliberately leaks the implementation detail of "we are using a handlemap". I just want to make sure we consider the tradeoffs involved.

unifying (ha!)

I will be really pleased if we come up with another name for a thing that keeps showing up in conversations in the same way that "sync up (ha!)" has been doing for our team for years 😆

@rfk
Copy link
Collaborator

rfk commented Mar 17, 2021

We happen to currently use an Arc and a handlemap for [Threadsafe] interfaces,
but we might want to try changing to something like a Box in future for performance reasons.

It occurs to me in the light of day, that Arc<T> is basically a Box<T> with some extra refcounting stuff in the allocation, and in fact all the fun converting to/from a raw pointer that you can do with a Box<T> you can also do with Arc<T>. You can even easily check whether two Arc<T>s point to the same data, which would help the Prestige-style bidirectional mapping problem that @jhugman has mentioned in other issues.

So...maybe I wouldn't actually be too upset if we "leaned in" to the use of Arc<T> at the interface layer here in general.

@mhammond
Copy link
Member Author

So...maybe I wouldn't actually be too upset if we "leaned in" to the use of Arc<T> at the interface layer here in general.

Another option might be to allow the UDL to somehow say "I want an Arc<>" - so lean in to always supporting the use of an Arc, but don't quite commit to "interfaces marked as [Threadsafe] will forever use Arcs".

@mhammond
Copy link
Member Author

(OTOH though, I think being opinionated here is fine - there's no need for us to support theoretical future use-cases that will probably never exist)

mhammond added a commit to mhammond/application-services that referenced this issue Mar 17, 2021
…manager.

This patch works-around the lack of the functionlity described
in mozilla/uniffi-rs#417.

In short, it splits the store into 2 parts - the
uniffi-exposed "wrapper" and the actual implementation.
This "wrapper" wraps the implementation in an `Arc<Mutex>>`, so
that it can hand a clone of this `Arc<>` to the sync manager, so
that it can construct sync engines, causing us to end up with
multiple references to the `Store` being alive but without any
being actual `&` references.

(Thinking some more about it, I probably could have done this
slightly differently, and just wrapped the DB itself, but I
ended up somewhat automatically taking this route because this
is what the shape would naturally be if uniffi helped us.
I think passing the entire store around does make more sense
in the longer term anyway (eg, there might be other state beyond
the DB) and is what the other components do, but it does make
this patch bigger than it would otherwise be)

It also makes some changes in the sync manager, really just
enough to convince me that it will work - there will be plenty
of other places that will need similar treatment. I chose to
use the `SyncEngine` trait exclusively there, which I think makes
sense because ideally we'd use that trait *everywhere* to avoid the
Sync Manager knowing about the implementation (but it would still
require it to link to all those crates, so that's just a
code-simplicity issue)
@jhugman
Copy link
Contributor

jhugman commented Mar 17, 2021

For clarity, I'm not sure I understand the problem space enough to make an informed comment. I think this might be worth probing more deeply in a synchronous setting.

@rfk
Copy link
Collaborator

rfk commented Mar 17, 2021

So...maybe I wouldn't actually be too upset if we "leaned in" to the use of Arc at the interface layer here in general.

Cross-linking for reference, it turns out that when I said "wouldn't be too upset" what I actually meant was "am about to go on a deep dive about making Arc<T> a fundamental part of the contract between UniFFI and the Rust code": #419.

@mhammond, I think what I'd proposing there is a generalization of what you're talking about here, and if we like the general direction we could consider some sort of [Self=ByArc] annotation on the method receiver as a first step in that direction.

I think this might be worth probing more deeply in a synchronous setting.

Also, +💯 to talking it through in a synchronous setting if we feel like there's something here we want to move on.

mhammond added a commit to mhammond/application-services that referenced this issue Mar 23, 2021
This patch works-around the lack of the functionlity described
in mozilla/uniffi-rs#417.

In short, it splits the store into 2 parts - the
uniffi-exposed "wrapper" and the actual implementation.
This "wrapper" wraps the implementation in an `Arc<>`, so
that it can hand a clone of this `Arc<>` to the sync manager, so
that it can construct sync engines, causing us to end up with
multiple references to the `Store` being alive but without any
being actual `&` references.

The database itself continues to be wrapped in a `Mutex<>`

It also makes some changes in the sync manager, really just
enough to convince me that it will work - there will be plenty
of other places that will need similar treatment. I chose to
use the `SyncEngine` trait exclusively there, which I think makes
sense because ideally we'd use that trait *everywhere* to avoid the
Sync Manager knowing about the implementation (but it would still
require it to link to all those crates, so that's just a
code-simplicity issue)
mhammond added a commit to mhammond/application-services that referenced this issue Mar 23, 2021
This patch works-around the lack of the functionlity described
in mozilla/uniffi-rs#417.

In short, it splits the store into 2 parts - the
uniffi-exposed "wrapper" and the actual implementation.
This "wrapper" wraps the implementation in an `Arc<>`, so
that it can hand a clone of this `Arc<>` to the sync manager, so
that it can construct sync engines, causing us to end up with
multiple references to the `Store` being alive but without any
being actual `&` references.

The database itself continues to be wrapped in a `Mutex<>`

It also makes some changes in the sync manager, really just
enough to convince me that it will work - there will be plenty
of other places that will need similar treatment. I chose to
use the `SyncEngine` trait exclusively there, which I think makes
sense because ideally we'd use that trait *everywhere* to avoid the
Sync Manager knowing about the implementation (but it would still
require it to link to all those crates, so that's just a
code-simplicity issue)
mhammond added a commit to mhammond/application-services that referenced this issue Mar 24, 2021
This patch works-around the lack of the functionlity described
in mozilla/uniffi-rs#417.

In short, it splits the store into 2 parts - the
uniffi-exposed "wrapper" and the actual implementation.
This "wrapper" wraps the implementation in an `Arc<>`, so
that it can hand a clone of this `Arc<>` to the sync manager, so
that it can construct sync engines, causing us to end up with
multiple references to the `Store` being alive but without any
being actual `&` references.

The database itself continues to be wrapped in a `Mutex<>`

It also makes some changes in the sync manager, really just
enough to convince me that it will work - there will be plenty
of other places that will need similar treatment. I chose to
use the `SyncEngine` trait exclusively there, which I think makes
sense because ideally we'd use that trait *everywhere* to avoid the
Sync Manager knowing about the implementation (but it would still
require it to link to all those crates, so that's just a
code-simplicity issue)
mhammond added a commit to mozilla/application-services that referenced this issue Mar 25, 2021
#3935)

This patch works-around the lack of the functionlity described
in mozilla/uniffi-rs#417.

In short, it splits the store into 2 parts - the
uniffi-exposed "wrapper" and the actual implementation.
This "wrapper" wraps the implementation in an `Arc<>`, so
that it can hand a clone of this `Arc<>` to the sync manager, so
that it can construct sync engines, causing us to end up with
multiple references to the `Store` being alive but without any
being actual `&` references.

The database itself continues to be wrapped in a `Mutex<>`

It also makes some changes in the sync manager, really just
enough to convince me that it will work - there will be plenty
of other places that will need similar treatment. I chose to
use the `SyncEngine` trait exclusively there, which I think makes
sense because ideally we'd use that trait *everywhere* to avoid the
Sync Manager knowing about the implementation (but it would still
require it to link to all those crates, so that's just a
code-simplicity issue)
@rfk
Copy link
Collaborator

rfk commented May 21, 2021

I believe we have broad agreement that what's being requested here is a subset of what we'll implement as a result of #419, so I'm going to close this issue out.

@rfk rfk closed this as completed May 21, 2021
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

No branches or pull requests

3 participants