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

Always send large futures to heap #6826

Merged
merged 2 commits into from
Sep 16, 2024

Conversation

mhils
Copy link
Contributor

@mhils mhils commented Sep 5, 2024

Motivation

In #4009, task::spawn was changed to send tasks to the heap to prevent stack overflows in debug mode.
The initial proposal was to do this unconditionally in debug mode:

if cfg!(debug_assertions) {

but based on a suggestion from @Darksonn this was then changed to

if cfg!(debug_assertions) && std::mem::size_of::<T>() > BOX_FUTURE_THRESHOLD {

As detailed in #6057, this creates a bit of a footgun where code works great in debug builds but then fails in release mode. This can be surprisingly hard to track down (#6057 (comment)).

Solution

Instead of only doing this for debug builds only, I propose we change the condition to

if std::mem::size_of::<T>() > BOX_FUTURE_THRESHOLD {

only so that debug and release builds work the same way.

@Darksonn mentioned that there may be other - better - ways to fix this in #6057 (comment). I don't know what the alternative would be, but until that is tackled I think this here could be better than the current implementation. :)

@Darksonn Darksonn added A-tokio Area: The main tokio crate M-runtime Module: tokio/runtime labels Sep 5, 2024
@Darksonn
Copy link
Contributor

Darksonn commented Sep 5, 2024

This would be somewhat unfortunate. Because of inlining, release mode can handle much larger futures than debug mode, and if we can store the future directly in the task allocation without indirection, then that really is for the best performance-wise.

@mhils
Copy link
Contributor Author

mhils commented Sep 5, 2024

Thanks. When you say "can handle much larger futures", do you have any intuition on how much larger? For context, we tripped over a 65k future. Does it make sense to conceptually do something like this?

    pub(crate) const BOX_FUTURE_THRESHOLD: usize = if cfg!(debug_assertions)  { 2048 } else { 16384 };

16384 is what clippy uses for the (off-by-default clippy::large_futures), but otherwise I'm making up numbers here. Do we have any benchmarks to assess impact?

@Darksonn
Copy link
Contributor

Darksonn commented Sep 9, 2024

I guess we can take it with a larger size in release mode.

this should make most stack overflows go away while not impacting the vast majority of code
Copy link
Contributor

@Darksonn Darksonn left a comment

Choose a reason for hiding this comment

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

Thanks.

@Darksonn Darksonn merged commit b5de84d into tokio-rs:master Sep 16, 2024
81 checks passed
@mhils mhils deleted the no-more-stackoverflow branch September 16, 2024 21:24
hds added a commit that referenced this pull request Oct 8, 2024
In Tokio, the futures for tasks are stored on the stack unless they are
explicitly boxed, either by the user or auto-boxed by Tokio when they
are especially large. Auto-boxing now also occurs in release mode
(since #6826).

Having very large futures can be problematic as it can cause a stack
overflow. In some cases it might be desireable to have smaller futures,
even if they are placed on the heap.

This change adds the size of the future driving an async task or the
function driving a blocking task to the tracing instrumentation. In the
case of a future that is auto-boxed by Tokio, both the final size as well
the original size before boxing is included.

To do this, a new struct `SpawnMeta` gets passed down from where a
future might get boxed to where the instrumentation is added. This
contains the task name (optionally) and the original future or function
size. If the `tokio_unstable` cfg flag and the `tracing` feature aren't both
enabled, then this struct will be zero sized, which is a small improvement
on the previous behavior of unconditionally passing down an `Option<&str>`
for the name.

This will make this information immediately available in Tokio Console,
and will enable new lints which will warn users if they have large futures
(just for async tasks).

We have some tests under the `tracing-instrumentation` crate which test
that the `size.bytes` and `original_size.bytes` fields are set correctly.

The minimal version of `tracing` required for Tokio has been bumped from
0.1.25 to 0.1.29 to get the `Value` impl on `Option<T>`. Given that the current
version is 0.1.40, this seems reasonable, especially given that Tracing's MSRV
is still lower than Tokio's in the latest version.
kodiakhq bot pushed a commit to pdylanross/fatigue that referenced this pull request Oct 23, 2024
Bumps tokio from 1.40.0 to 1.41.0.

Release notes
Sourced from tokio's releases.

Tokio v1.41.0
1.41.0 (Oct 22th, 2024)
Added

metrics: stabilize global_queue_depth (#6854, #6918)
net: add conversions for unix SocketAddr (#6868)
sync: add watch::Sender::sender_count (#6836)
sync: add mpsc::Receiver::blocking_recv_many (#6867)
task: stabilize Id apis (#6793, #6891)

Added (unstable)

metrics: add H2 Histogram option to improve histogram granularity (#6897)
metrics: rename some histogram apis (#6924)
runtime: add LocalRuntime (#6808)

Changed

runtime: box futures larger than 16k on release mode (#6826)
sync: add #[must_use] to Notified (#6828)
sync: make watch cooperative (#6846)
sync: make broadcast::Receiver cooperative (#6870)
task: add task size to tracing instrumentation (#6881)
wasm: enable cfg_fs for wasi target (#6822)

Fixed

net: fix regression of abstract socket path in unix socket (#6838)

Documented

io: recommend OwnedFd with AsyncFd (#6821)
io: document cancel safety of AsyncFd methods (#6890)
macros: render more comprehensible documentation for join and try_join (#6814, #6841)
net: fix swapped examples for TcpSocket::set_nodelay and TcpSocket::nodelay (#6840)
sync: document runtime compatibility (#6833)

#6793: tokio-rs/tokio#6793
#6808: tokio-rs/tokio#6808
#6810: tokio-rs/tokio#6810
#6814: tokio-rs/tokio#6814
#6821: tokio-rs/tokio#6821
#6822: tokio-rs/tokio#6822
#6826: tokio-rs/tokio#6826
#6828: tokio-rs/tokio#6828
#6833: tokio-rs/tokio#6833
#6836: tokio-rs/tokio#6836
#6838: tokio-rs/tokio#6838
#6840: tokio-rs/tokio#6840


... (truncated)


Commits

01e04da chore: prepare Tokio v1.41.0 (#6917)
92ccade runtime: fix stability feature flags for docs (#6909)
fbfeb9a metrics: rename *_poll_count_* to *_poll_time_* (#6924)
da745ff metrics: add H2 Histogram option to improve histogram granularity (#6897)
ce1c74f metrics: fix deadlock in injection_queue_depth_multi_thread test (#6916)
28c9a14 metrics: rename injection_queue_depth to global_queue_depth (#6918)
32e0b43 ci: freeze FreeBSD and wasm-unknown-unknown on rustc 1.81 (#6911)
1656d8e sync: add mpsc::Receiver::blocking_recv_many (#6867)
c9e998e ci: print the correct sort order of the dictionary on failure (#6905)
512e9de rt: add LocalRuntime (#6808)
Additional commits viewable in compare view




Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

@dependabot rebase will rebase this PR
@dependabot recreate will recreate this PR, overwriting any edits that have been made to it
@dependabot merge will merge this PR after your CI passes on it
@dependabot squash and merge will squash and merge this PR after your CI passes on it
@dependabot cancel merge will cancel a previously requested merge and block automerging
@dependabot reopen will reopen this PR if it is closed
@dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
@dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
@dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
@dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
@dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate M-runtime Module: tokio/runtime
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants