-
Notifications
You must be signed in to change notification settings - Fork 234
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
ADR-0008: Freeing objects and resources #1787
Conversation
95dbda3
to
bb54a71
Compare
|
||
## Considered Options | ||
|
||
### [Option 1] User-defined close method |
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.
Sammy suggested this one to me recently and I have to admit that I'm biased towards it. Please point out anything I'm missing because of that bias, but I like it because a) it's the least opinionated and b) AFAICT, it's the only solution that also works for callback interfaces.
|
||
* If an object does not hold a resource, then we should be able to use the GC to free it. | ||
* The solution should work for both Rust objects passed to the foreign language and foreign objects | ||
passed to Rust. |
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.
IMO at least, but this isn't a hard requirement. We could chose one option for objects and a different option for callback interfaces (although trait interfaces could get tricky).
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'm actually against this, I think in contexts where RAII is available, we should use it. But maybe I'm misunderstanding something as I wasn't even aware that freeing of callback interface objects was ever a problem, and this ADR doesn't really provide any details as to why it's a problem (I would have thought dropping the Box<dyn Trait>
would do the right thing,though now I'm wondering how it's even possible that Rust gets unique ownership over such an object, I guess it actually doesn't and the Box
points to a GC-managed 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.
Exactly. A callback interface can hold a resource. With the current code, when you drop the Rust object then we will drop the GC-reference, but that doesn't mean the resource will be released. Best-case scenario it will be released at some time in the future. Worst-case scenario it won't be cleaned up at all, because the Kotlin user expected an explicit close()
method to be called. In practice, is the library would probably call that explicit close()
method before dropping the Box
, which is why I'm thinking maybe we should just make that the norm.
That said, @arg0d points out that you could connect dropping the Box
to a close()
method. I'm going to update the ADR with that option.
|
||
## Context and Problem Statement | ||
|
||
UniFFI must support objects that hold resources like database connections, file handles, network sockets, etc. This is difficult, because objects like this are handled differently by different languages: |
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.
Why are memory allocations not mentioned? Seems like the most frequent / obvious kind of resource basically every UniFFI API handles.
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 always think the same thing, but most Java/Kotlin/Python programmers make a distinction between memory and other types of resources. Memory is abundant enough for the GC to clean up, other resources are scarce and should be cleaned up immediately.
I don't think there's a hard line here though, if you allocate a gig of memory then that should probably be considered a "scarce resource" and if we picked option 1, the close()
method would free that memory.
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.
Java/Kotlin/Python programmers make a distinction between memory and other types of resources
this sentence clears up ideas in this ADR, you should include it in the context.
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 also had the same question at first, and wanted to share how I thought through it, in case it's helpful for context or other folks!
IIUC, from the perspective of the foreign code, a UniFFI object is "a pointer to some memory that exists outside of the GC heap". Because it's outside the GC heap, it can't be GCed automatically.
That's why these objects need some kind of destructor, either:
- An explicit method, like
close()
ordestroy()
, or... - A finalizer that's automatically called by the foreign language's runtime. In the JVM, that's
java.lang.Object.finalize()
orCleaner
; in Python, that's__del__
; in Go, that'sruntime.SetFinalizer
, and so on.
On the Rust side, the object might have a handle to a "resource"—like a database connection, a file handle, etc.—or it might not, but that's totally opaque to the foreign code.
In that sense, I don't think UniFFI's AutoCloseable
machinery today is concerned about "resources" specifically—it's a destructor that tells Rust to drop the object, and free up the memory it uses. Whether or not dropping the object also happens to release "resources" on the Rust side—for example, a rusqlite connection implementing Drop
to automatically close the connection—is an implementation detail from Kotlin's perspective.
reasons: | ||
|
||
* It is specific to Kotlin, but many languages face similar issues. | ||
* All objects get this handling, when only some need 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.
Don't all objects need to be deallocated to avoid leaking memory?
* It is specific to Kotlin, but many languages face similar issues. | ||
* All objects get this handling, when only some need it. | ||
* This system is tied to decision to *not* support freeing UniFFI objects via GC-based systems | ||
like `Cleaner`, which is not correct for some interfaces (see #1394) |
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.
Idk what "see #1394" is supposed to do here, it has lots of comments and I didn't find any obvious "using Gc would be wrong" statements there so far, just lots of discussions about GC as the only way of cleaning up being insufficient in some cases.
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 linked #1394 for the opposite reason: to show that sometimes we should rely on the GC. The sentence is confusing because there's a weird double-negative in there though. I'll try to clean that up.
* Bad, because it relies on the `Cleaner` Java API, which is not available on very old versions of | ||
Android. | ||
|
||
### [Option 2] Resource and Object as separate types |
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 say it also has the downside of adding more complexity. The other two options don't really introduce any new concepts in the public API from my POV, this one does.
|
||
* If an object does not hold a resource, then we should be able to use the GC to free it. | ||
* The solution should work for both Rust objects passed to the foreign language and foreign objects | ||
passed to Rust. |
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'm actually against this, I think in contexts where RAII is available, we should use it. But maybe I'm misunderstanding something as I wasn't even aware that freeing of callback interface objects was ever a problem, and this ADR doesn't really provide any details as to why it's a problem (I would have thought dropping the Box<dyn Trait>
would do the right thing,though now I'm wondering how it's even possible that Rust gets unique ownership over such an object, I guess it actually doesn't and the Box
points to a GC-managed object?).
### [Option 1] User-defined resource close method | ||
|
||
* Good, because it's the only option that supports callback interfaces (and trait interfaces in the | ||
future). If a foreign object is passed into Rust and that object holds a resource, Rust needs to |
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.
Foreign object holding a resource would be a callback implementation on foreign language side, right? Any resources being held by the callback are user defined, right? So it should be possible to generate a "close" method for the callback that is called when Rust invokes callback with 0
to remove the callback from handle map. User could then implement the appropriate logic to cleanup any resources being held by the callback. Closing the callback seems to be already implemented, only close
method is lacking in the interface to allow foreign bindings to close further resources.
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 point, I'm going to update the docs with this option.
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.
Foreign object holding a resource would be a callback implementation on foreign language side, right?
I think we want to generalize callbacks into more generic traits though - eg, wrap them in an Arc<>
such that Rust code can be blissfully unaware whether a trait implementation is foreign or rust implemented.
5005577
to
8d44745
Compare
|
||
## Context and Problem Statement | ||
|
||
UniFFI must support objects that hold resources that should be cleaned up promptly like database connections, file handles, network sockets, large memory allocations, etc. |
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.
Thanks heaps for driving this forward Ben - it's a discussion we keep having that never seems to make progress!
Initially I'm just putting down some thoughts on the problem statement, as I believe tightening this up might help the rest.
#1394 says we could add "finalizer support to make a 'best effort' attempt at freeing what it can while leaving the auto-closeable pattern in place" - I'm not aware of us exploring that, nor how this ADR might change if that turned out to be successful.
This section starts off talking about all bindings in general, but then fairly quickly pivots into just Kotlin. While Kotlin is important, I think it's still worth a bit more of an explanation about how the other "important" bindings work. For example, it seems that Python also shares the same general problem - a context-manager is available, but this doesn't really work for long lived references. #8 implies Swift does not have this problem. C# and Go seem worth mentioning too as I suspect they might end up being very popular UniFFI bindings.
Upon re-reading this ADR, I think your "Option 1" can be restated as "Do nothing. It is up to the designer of the API exposed via UniFFI to take care of this. An option might be to add explicit "close()" methods, but that's all on you, we don't care". If that's accurate, maybe update the wording to be more explicit/brutal? We've also been talking about "transitive requirements (eg, enum holding a dictionary which holds a resource - there's nothing generic you can do on the enum to clean this tree up) - if the intention is also to offer no help there and leave that to the API designer it might be worth calling that out.
I also don't really understand the callback example - or more specifically, how the callback example is any different from the general problem. If you consider a pure-python program which takes some arbitrary object, there's also no generic way for the holder of that object to release the resources held by that arbitrary object - but that's not generally considered a problem - the solutions are either (a) know the type, so, eg, if it's a file you know you can .close() it, or (b) don't do that. IOW, the responsibility for this on the designer of the API so that either (a) the API has an explicit close-type method, or (b) don't require the Rust code to keep a long-lived reference to an object holding resources you care about?
tl;dr - I think I'm also in favour of Option 1 if I understand it correctly - and if I do, I'd suggest:
- Making that option even more explicit and "brutal" - ie, explicitly call out "this is your problem, UniFFI doesn't help"
- Explicitly call out the transitive case (ie, if your enums hold objects which manage resources, you are probably going to have a bad time, but UniFFI isn't going to try and stop you)
- Either remove most of the callback language (ie, replace it with something like "this is just a slightly different view of the exact same problem") or clarify how callbacks differ.
- Make the problem statement slightly less Kotlin specific, or explain how, eg, Python is different.
And probably most importantly, work out which Kotlin people to put this in-front of. Most UniFFI core contributors know enough Kotlin to be dangerous, but probably not quite enough to know how viable this will be in practice.
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.
Thanks, I think these are great suggestions. I "brutalized" option 1 a bit, I could have gone farther, but I think it makes the point pretty well now. I added some text about the transitive case and also other languages. I agree it shouldn't be Kotlin specific.
I moved callbacks to their own section and tried to elaborate a bit more. On the one hand, I think that it's all part of the same problem, but I also think they're important because they provide extra complications for any automatically generated solution.
Next step is to find some more Kotlin people...
* Use the `Cleaner` API on Kotlin to automatically free UniFFI pointers for Kotlin objects. | ||
Note that this combines with the previous bullet. `close()` releases the resource, the Cleaner | ||
action frees the heap allocation. |
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 didn't know anything about this API, nor much about "oldschool" finalizers, but https://stackoverflow.com/questions/52879761/should-java-9-cleaner-be-preferred-to-finalization points towards Cleaner
really being the best choice here.
d17b6b7
to
66fa482
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.
Objects can hold resources that should be cleaned up promptly like database connections, file handles, network sockets, large memory allocations, etc.
Many languages, like Kotlin, Java and Python make a distinction between these types of resources and GC heap allocations that is reflected in the API.
Programmers typically expose aclose
method and languages often offer constructs to assist in using these like context managers on Python or theuse
extension function on Kotlin.
However, on Rust and other RAII-based languages, these resources are typically released in the destructor rather than with a dedicated API.
I'm not sure that this is the problem to which the solution proposed is solving.
The problem we have been discussing is this:
- most language expose some kind of finalizer where we can inform Rust to free up memory when the language no longer needs it:
- In Swift we implement
deinit
- In Python we implement
__del__
- In Ruby we define with
ObjectSpace.define_finalizer
- In Swift we implement
In Kotlin, we might implement java.lang.Object.finalize
(spec). However:
- it has been deprecated since JDK 9,
- anecdotally, it has been observed as the source of crashes on Android.
- before it was deprecated, it was the subject of heavy warnings against using it– so is a source of JVM diversity and little usage.
Indeed, the Android documentation on java.lang.Object#finalize
is very instructive here, and worth quoting extensively:
Note that objects that override finalize are significantly more expensive than objects that don't. Finalizers may be run a long time after the object is no longer reachable, depending on memory pressure, so it's a bad idea to rely on them for cleanup. Note also that finalizers are run on a single VM-wide finalizer thread, so doing blocking work in a finalizer is a bad idea. A finalizer is usually only necessary for a class that has a native peer and needs to call a native method to destroy that peer. Even then, it's better to provide an explicit close method (and implement Closeable), and insist that callers manually dispose of instances. This works well for something like files, but less well for something like a BigInteger where typical calling code would have to deal with lots of temporaries. Unfortunately, code that creates lots of temporaries is the worst kind of code from the point of view of the single finalizer thread.
If you must use finalizers, consider at least providing your own ReferenceQueue and having your own thread process that queue.
For our case, the mitigations from (in chronological order) Josh Bloch's Effective Java, the JDK deprecation of finalize and the Android doc all point in the same direction:
- implement
Closeable
andAutoCloseable
, and insist our users manually dispose of their instances. - implement a Cleaner, or something else that polls a
ReferenceQueue
. - don't rely on either happening as soon as the object drops out of scope.
So far, we have implemented AutoCloseable
and Closeable
; and this is the source of impedance mismatch and confusion.
-
As @linabutler alludes, JVM heap memory isn't something that Java/Kotlin programmers need to worry about, so most things that needs a finalizer are open and closeable, e.g. sockets, files, databases. However non-heap memory doesn't fall cleanly into either of these categories: it does need cleanup, but isn't openable and closeable. It feels strange to
close
an only-in-memory object, but that's currently what we advise to avoid memory leaks. -
To the untrained eye,
AutoCloseable
seems to do something useful, however it is really only useful in a specific use case: namely interacting with the so-calledtry-with-resources
.
Kotlin doesn't have a try-with-resources, but does implement use
which does the same job. The implementation of use
is worth looking at, if only to see how little the tooling around AutoCloseable
and Closeable
gives use.
- Part of uniffi's value proposition is that we try and expose Rust crates to foreign languages in such a way as using them is indistinguishable from using a foreign language equivalent. We should look at the existence of
Closeable
and forcing users to explicitly callclose
oninterface
structs is a leak in the abstraction we should seek to close.
TL;DR:
- We should move away from using
Closeable
andAutoCloseable
, in favour of a cleaner based approach, to bring up to parity with the other languages we support. - We should not replicated
close
methods for other languages. If__del__
is deprecated in Python we should investigate alternatives (e.g.ContextManager
).
WDYT?
Thanks for the detailed reply James! A couple of questions: Re (1), I always thought that Re (2) I don't quite understand what you are saying there. Eg, re Python, are you proposing something should change re Indeed, combining these points, my take away would be:
(*I'm even less familiar with Swift than I am Kotlin, but IIUC swift objects use reference counting, so I can't see why the same arguments don't apply there - I've just no idea if there's a protocol or some other technique that might be suitable) |
@mhammond I agree with basically everything you said, except for one part:
I think there's a tension between these 2 statements. If we're suggesting to the user to define a I think we should start by simply not trying to implement AutoClosable, ContextManager, etc. Later on, we could let users opt-in to this with something similar to how we all users to implement standard Rust traits. If an interface defines |
I guess I was suggesting that we can document something simple like: A reference to the corresponding Rust object will be dropped when the object is garbage collected. You can force the reference to be dropped earlier by using the object in a
Are they mutually exclusive? We are always going to be dealing with an OTOH, I guess an alternative is to stop supporting the ability for the Rust object to be "disconnected" from the foreign object entirely. According to the template code, after (Apologies if that was the intent of James's proposal which I missed!) |
Question (1)
For clarity: we don't need to implement Similarly, the only thing @bendk is correct, both interfaces are almost identical: they each ask you to implement a single Question (2) I must admit, my python is not what it should be. If My point however still stands: we should bring Kotlin support to the same level we have for Python, Swift and Ruby— implicit memory handling is done implicitly, and any use-case-specific resource closing (where necessary/desired) is done explicitly in userland.
This is the point @linabutler so eloquently made this week– by defauit we should avoid the state where we have orphaned/dangling Foreign Language objects that are still reachable, but effectively dead because the backing Rust object has been invalidated by an explict call from the foreign side; this is facility that the users can build for themselves, but we shouldn't enable it by default. |
66fa482
to
d6a93c2
Compare
Thanks for all the help on this one everyone. After discussing this more @jhugman and @mhammond, I just pushed an updated ADR (most of it written by @mhammond). The updated ADR:
|
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.
Very nice rewrite of the ADR text!
09f359e
to
c3b0c9c
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.
Thanks heaps Ben! I have entirely nits (and a type CI job is an interesting idea :)
I guess we also need to consider making a decision here. I believe you and I are clearly on the "option 1" fence, and I doubt that will be controversial, so I guess we should actually fill that part of it in, then get formal approval from a few people. "Deciders" should probably just be something like "Uniffi developers, as approved in PR-XXXX" or similar? Either way, thanks for staying with this!
c3b0c9c
to
b33d4b0
Compare
This is looking great to me. I made the edits based on @mhammond's suggestions, updated the doc to say we decided on option [1], updated the status/deciders lines, etc. I added those because it feels like we're close to a decision to me, but if anyone has concerns and wants to explore the other options, please add a comment. There's definitely still time to discuss more. |
76e12aa
to
b603186
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.
A few comments on the form and problem statement of the ADR, but nothing to stand in the way of the action proposed.
* Existing `deinit` implementation is suitable so nothing changes here. | ||
* Once we have the "Foreign Sugar" opt-in mechanism in place we will generate context managers for such objects. | ||
|
||
## Kotlin |
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: Burying the lede. Considering we don't generate a close
method for any other language except Kotlin, and every other language has destructor/finalizer arrangements that we already use, it is surprising that Kotlin is only spoken about in the last 5 lines of the ADR.
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 there's a tension her.e. As a practical matter, this ADR is essentially just suggestion changes to the Kotlin code. As a theoretical matter, I think it's important to discuss the topic in general and separate it from all the details about finalizers, cleaners, etc.
I tried to strike a balance by mentioning this at the top of the doc, do you think that addresses the concern?
b603186
to
cdb1b62
Compare
I don't think there's been any concerns with the proposal, just suggestions about the ADR wording. If anyone wants to argue for one of the other options, please speak up. |
I just realized something about the current Kotlin system: Since user code can cause the underlying handle to be destroyed, it's risky to ever pass it to Rust. Most of the ObjectRuntime.kt file is dedicated to avoiding this for method receivers, but doesn't the same issue apply to function/method arguments? IOW, if I pass an object to a function, what's to stop another thread from calling |
oops not sure why GitHub removed review from @arg0d, wanted remove the request for myself only 😄 |
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.
Super late to this party, but option 1 was something I supported and so LGTM!
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.
Thanks so much, @bendk!
## Context and Problem Statement | ||
|
||
UniFFI objects always hold a reference that needs to be released and sometimes hold a resource that needs to be closed. | ||
The current design conflates the two issues. |
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 a great summary of the problem! I think we also got confused by the conflation when discussing the options, and this clarifies it really well!
This pattern is so common that some languages offer syntactic sugar for this express purpose. | ||
For example, Python offers a "context manager", Kotlin offers the `use` extension for objects that implement `Closable`, etc. | ||
|
||
Ideally, UniFFI would offer a way so consumers can opt-in to implementing this capability. |
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 really like your separation of references and resources, and having UniFFI release references makes a lot of sense to me! But I'm not sure I follow the conclusion that UniFFI needs to concern itself with resources, and was wondering if we could talk more about that.
If a UniFFI consumer wants to define an interface
type T
that holds a resource—like a file or a database connection—they could manually define a close
method, and have the Rust implementation of T
hold a Mutex<Option<Resource>>
that the implementation of T::close()
method would take()
to close the resource early. They could also impl Drop for T
, which would take care of closing the resource once the last Arc<T>
reference was released.
It's definitely true that, without an opt-in sugar, the UniFFI-generated Kotlin binding for T
couldn't implement Closeable
or AutoCloseable
—T
would just have a plain, vanilla close()
method. (And, similarly, the Python binding would have a regular close
method, and wouldn't be a context manager). But I wonder if that's okay?
I'm also wondering what this opt-in sugar would look like for languages like Swift (and maybe Ruby?) that have different idioms for resources. For example, Swift's deinit
is the closest, but ARC doesn't guarantee precise lifetimes, and Swift seems to prefer the "with
-prefixed function that takes a closure" idiom for resources (withLock
, withExtendedLifetime
, withUnsafe{Mutable}{Pointer, Bytes}
).
Sorry that was so wordy! I guess I'm wondering—would the opt-in sugar for resources offer enough benefits as a UniFFI feature, than having consumers handle resource management themselves?
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.
Great question. Right now, I fall on the side of probably implementing Closeable
and/or AutoClosable
on Kotlin, maybe implementing context managers on Python, and that's it. I'm not sure what others feel like or even if that's where I'll end up once we actually test it out.
I think the line that says "The exact mechanics for this are beyond the scope..." was supposed to communicate that we don't really have a final decision here, however the other line does seem to imply that we want to do something. I softened that a bit to just say, we'll investigate it and decide later.
a6aa2f5
to
20ebb40
Compare
20ebb40
to
3ebc07a
Compare
|
||
## Kotlin | ||
|
||
* UniFFI will implement a mechanism that automically frees Rust references when the corresponding foreign object is destroyed. |
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 one of the last issues is to update this statement so that it's clear that UniFFI would handle this behind-the-scenes and neither the library author or consumer would need to write any code to make that happen. Does this new wording make that clear?
Looks like we're all agreed on the approach. Is there anything outstanding to discuss? If not, I'll aim to merge this tomorrow. |
Not something that should have stopped merging the ADR, but your concern about the Kotlin implementation seems like something that needs more investigation, in case you had forgotten about it again because nobody reacted. |
I think that gets fixed once freeing the reference is tied to |
We've been discussing this one quite a bit lately and it seems like a great time to make a decision. Here's the start of an ADR which lays out all the possibilities I've heard so far, I'd love to hear what others think. I'm including everyone that I can remember having a discussion about this, but all voices are welcome.