Skip to content

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

Merged
merged 3 commits into from
Oct 15, 2018

Conversation

andreasots
Copy link
Contributor

Since the LocalWaker is dropped immediately after polling the task01::Task can be stored on the stack.

Fixes #1283.

@Arnavion
Copy link
Contributor

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())
Copy link
Contributor

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nemo157 said in #1283

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?

Copy link
Member

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?

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Member

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?

Copy link
Member

@Nemo157 Nemo157 Oct 15, 2018

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:

  1. tokio::run calls into <futures::task_impl::Spawn<T>>::enter which sets up the Task in TLS
  2. futures_util::compat::compat03to01::<impl futures::future::Future for futures_util::compat::compat::Compat<Fut>>::poll creates a CurrentRef, then calls the wrapped future (the async block) with it as the LocalWaker in the context
  3. the std libraries async layers are passed through (probably shouldn’t have fully snipped those)
  4. futures_util::compat::compat01to03::<impl core::future::future::Future for futures_util::compat::compat::Compat<Fut>>::poll takes a reference to the current LocalWaker on the context and updates the Notify on the task to be this wrapped reference as WakerToHandle
  5. <tokio_timer::delay::Delay as futures::future::Future>::poll calls futures::task_impl::current, this attempts to acquire an owned handle to the current Notify (again I did some overzealous snipping, see the linked gist in the issue for the full stacktrace)
  6. 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 the LocalWaker it has
  7. to clone the waker (into a CurrentOwned) <futures_util::compat::compat03to01::CurrentRef as core::task::wake::UnsafeWake>::clone_raw calls futures::task_impl::current to get the current task
  8. goto 6

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's great!

@cramertj cramertj merged commit 4ce0fba into rust-lang:master Oct 15, 2018
@cramertj
Copy link
Member

Thanks so much for the fix! I'll get a release out today.

@andreasots andreasots deleted the compat-stack-overflow branch October 16, 2018 04:43
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

Successfully merging this pull request may close these issues.

5 participants