-
Notifications
You must be signed in to change notification settings - Fork 658
Fix the stack overflow in the compat layers (#1283) #1286
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
Conversation
Store the `task01::Task` on the stack to avoid allocation.
Maybe also fix and uncomment the doctests in futures-util/src/compat/tokio.rs so that this doesn't happen again. |
|
||
impl Current { | ||
fn new() -> Current { | ||
Current(task01::current()) |
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.
So this brings us back to cloning the Task
on every single poll, even if the future doesn't need to clone it. Do we fully understand why the stack overflow is happening?
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.
The issue appears to be because CurrentRef delays getting the task until it's needed it waits too long and the task's waker has already been replaced with a NotifyWaker that tries to use the CurrentRef, making a loop. Removing CurrentRef and producing a CurrentOwned straight away when needed fixes it. I assume CurentRef is there because CurrentOwned can be expensive with the Arc and accessing task::current, maybe there's an alternative optimization that could be done and will be safe against task::current being changed under 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.
@seanmonstar This is probably a single atomic op, right? In comparison, the UnsafeWake::clone_raw
implementation requires actually allocating a new Arc
-- that seems far more expensive to me, so I wouldn't think this to be that big of a deal. Do you disagree?
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.
If not, I'd prefer to land this and issue a release to get things working again, then iterate towards a better (but still correct) solution.
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 good with getting a fix first. It'd help if the fix includes a test that was failing, so new attempts at optimizing won't regress again.
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 change includes a test here -- does that work for you?
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.
From the example and stacktrace in the linked issue the following happens:
tokio::run
calls into<futures::task_impl::Spawn<T>>::enter
which sets up theTask
in TLSfutures_util::compat::compat03to01::<impl futures::future::Future for futures_util::compat::compat::Compat<Fut>>::poll
creates aCurrentRef
, then calls the wrapped future (theasync
block) with it as theLocalWaker
in the context- the
std
libraries async layers are passed through (probably shouldn’t have fully snipped those) futures_util::compat::compat01to03::<impl core::future::future::Future for futures_util::compat::compat::Compat<Fut>>::poll
takes a reference to the currentLocalWaker
on the context and updates theNotify
on the task to be this wrapped reference asWakerToHandle
<tokio_timer::delay::Delay as futures::future::Future>::poll
callsfutures::task_impl::current
, this attempts to acquire an owned handle to the currentNotify
(again I did some overzealous snipping, see the linked gist in the issue for the full stacktrace)- to create the owned handle (
NotifyWaker
)futures_util::compat::compat01to03::<impl core::convert::From<futures_util::compat::compat01to03::WakerToHandle<'a>> for futures::task_impl::NotifyHandle>::from
attempts to clone theLocalWaker
it has - to clone the waker (into a
CurrentOwned
)<futures_util::compat::compat03to01::CurrentRef as core::task::wake::UnsafeWake>::clone_raw
callsfutures::task_impl::current
to get the current task goto 6
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.
Yep, that's great!
Thanks so much for the fix! I'll get a release out today. |
Since the
LocalWaker
is dropped immediately after polling thetask01::Task
can be stored on the stack.Fixes #1283.