-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Context and Waker might be accidentally Sync
#66481
Comments
Even if this weren't a breaking change, I'd want to see some really good evidence these optimizations would matter for real world code before choosing to make these types non- |
|
I have an example of where the current API is resulting in performance issues: I am trying to implement a combined Executor/Reactor around a winit event loop. To wake up the event loop from a different thread we have to post a message to the event loop which can be a non-trivial operation. But on the same thread, we know that we don't need to wake up the eventloop, so we can use much cheaper mechanisms like an atomic bool. |
@worktycho But that would be true if wakers were just |
I think the point is that |
I don't think it should be the judgement of the libs team (or any of us) to determine whether something is good enough and not needs to be further optimized for any use-case. Rusts goal as a language is to enable zero cost abstractions. This requires in my opinion to not be opinionated on any design choices which have a performance/footprint impact. I think some of the decisions that have been taken in the async/await world however are opinionated, and will have an impact on use-cases that currently are still better implemented with different mechanisms. I do not really want to elaborate on anything more particular, because I fear this would bring the discussion back to a arguing whether any X% performance degradation is still good enough. That is an OK decision to have for a software project whichs needs to decide whether Rusts async/await support is good enough for them, and whether they have to apply workarounds or not. But it's not a discussion which will move the issue here any more forward, because for yet another project the outcoming of the discussion might be different. PS: I do think it's okay and good if libraries like tokio or async-std are being more opinionated about what threading models they support, and they might sacrifice performance gains in rare usage scenarios for easy of use. But |
This is a trade off: Context is either Sync or it isn't, some users benefit from one choice (they can use references to Context as threadsafe) and some users benefit from the other choice (they can have nonthreadsafe constructs inside the Context construct). Ultimately the libs team has to decide one way or the other on these points in the API where there is a trade off between thread safety and not. However, this decision has already been made and it would be a breaking change to change it, so this discussion is moot. |
Do you have an example where this is actually done within the ecosystem, or a use-case for it? As far as I can tell, this would require spawning a thread using something like |
A common way a user could depend on a type being |
I think its a fair point that we didnt intentionally make context send and sync, and that this precludes adding more fields to context that are not send and sync, and that since context is just a buffer for future changes, it would probably have been better to make it non-threadsafe. But now its a breaking change. If crater showed no regressions and there's no known patterns it nullifies, I would be open to changing this about Context personally, but I am somewhat doubtful the libs team as a whole would approve making a breaking change to support hypothetical future extensions. If anyone on the thread wants to pursue a change to context (not waker), they should get crater results as the next step of the conversation. |
There is no discussion about As @kabergstrom mentioned, the most likely way to see this behavior is some scoped thread API being used inside And yes, the impact on |
Why would these be "certainly better" than just calling |
To be a little more expansive: the scoped threadpool you're talking about could very well not be scoped inside a poll method - rather, the waker could be cloned once and then owned by one thread and referenced by many other scoped threads in some sort of threadpool construct for CPU bound tasks. This seems like a perfectly valid implementation which allows you to divide up the work among many threads without cloning the waker many times. This is potentially an optimization. I don't think this optimization is very important, but I don't think the optimizations allowed by making waker |
We discussed this at the recent Libs meeting and felt that deferring to @rust-lang/wg-async-foundations would make sense here. |
Though I don't have the bandwidth to carry this, this definitely seems interesting. Seeing projects such as I don't know if we should make this a priority, but at least we probably shouldn't close it just yet. |
I think bringing |
As the person who originally introduced |
The cited discussion in the RFC justifies making |
This was recently closed, however the issue mentions both There is good reason that |
(NOT A CONTRIBUTION) Waker supports wake_by_ref, and so it is possible to pass Supporting wakers that are either not Send or not Sync will best be done by adding new APIs to Context and a new LocalWaker type. |
Forgive me the naive question, but how does a
Which is only useful if |
(NOT A CONTRIBUTION)
Future::poll does not take Waker as an argument, Future::poll takes Context. Context could have the ability to set a LocalWaker, so that an executor could set this. Reactors which operate on the same thread as the future that polls them could migrate to using the LocalWaker argument instead of Waker. Here is a pre-RFC that someone wrote with a possible API: https://internals.rust-lang.org/t/pre-rfc-local-wakers/17962
Commentary like this is both factually wrong and not helpful for the mood of the thread. |
One issue that came up in the discussion here is that the
Context
type implements Send + Sync.This might have been introduced accidentally.
Send
probably does not matter at all, given that users will only observe aContext
by reference. HoweverSync
has the impliciation that we will not be able to add non thread-safe methods toContext
- e.g. in order to optimize thread-local wake-ups again.It might be interesting to see whether
Send
andSync
support could be removed from the type. Unfortunately that is however a breaking change - even though it is not likely that any code currently usesContext
out of the directpoll()
path.In a similar fashion it had been observed in the implementation of #65875 (comment) that the
Waker
type is alsoSend
andSync
. While it had been expected forSend
- given thatWaker
s are used to wake-up tasks from different threads, it might not have been forSync
. One downside ofWaker
s beingSync
is that it prevents optimizations. E.g. in linked ticket the original implementation contained an optimization that while aWaker
was notclone
d (and thereby equipped with a different vtable) it could wake-up the local eventloop again by just setting a non-synchronized boolean flag in the current thread. However given that&Waker
could be transferred to another thread, and.wake_by_ref()
called from there even within the.poll()
context - this optmization is invalid.Here it would also be interesting if the
Sync
requirement could be removed. I expect the amount of real-world usages to be in the same ballpark as sending&Context
across threads - hopefully 0.But it's again a breaking change 😢
cc @Ralith , @Nemo157 , @cramertj , @withoutboats
The text was updated successfully, but these errors were encountered: