Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upGitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
Increasingly slow compilation as more levels of `async` are added in Rust 1.46 #75992
Comments
Possible duplicate of #72408? |
Noticed the same issue. A crate that compiled in 1.45 in about 4 minutes, seems to compile for forever in 1.46 (rustc utilized 100% of one core for at least 25minutes before I cancelled it) EDIT: 2 hours of compilation later, and no result yet |
Interestingly, this only happens with |
Well, it is a nearly perfect exponential fit! |
Got you an example on the playground (updated) Just adjust the number for the macro call here: async fn f() {
// 9: ~0.68s
// 10: ~0.83s
// 11: ~1.34s
// 12: ~2.68s
// 13: ~5.24s reached length limit -> #![type_length_limit="1228764"]
// 14: ~7.89s reached length limit -> #![type_length_limit="2457564"]
// 15: /playground/tools/entrypoint.sh: line 11: 7 Killed timeout --signal=KILL ${timeout} "$@"
async_recursive!(9, println!("done"))
} EDIT: My crate that "compiles forever" just failed after nearly 3hours of compiling. I don't know what caused the SIGKILL, but it wasn't me at least. $ time cargo test -p common --release
...
error: could not compile `messages`.
Caused by:
process didn't exit successfully: `rustc --crate-name messages --edition=2018 messages/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts --crate-type lib --emit=dep-info,metadata,link -C opt-level=3 -C metadata=0c091483b2a87fc1 -C extra-filename=-0c091483b2a87fc1 --out-dir /tmp/cargo-target/release/deps -L dependency=/tmp/cargo-target/release/deps --extern asn1rs=/tmp/cargo-target/release/deps/libasn1rs-7f0d3cb210b4f673.rmeta --extern futures=/tmp/cargo-target/release/deps/libfutures-3a0be1c37a28e9a8.rmeta --extern serde=/tmp/cargo-target/release/deps/libserde-d9d8359d95432fa4.rmeta --extern serde_derive=/tmp/cargo-target/release/deps/libserde_derive-26bde9a45b227be1.so --extern tokio=/tmp/cargo-target/release/deps/libtokio-c76c350ce32195b9.rmeta --extern tokio_postgres=/tmp/cargo-target/release/deps/libtokio_postgres-e63ce98e1edc8138.rmeta` (signal: 9, SIGKILL: kill)
real 161m7,663s
user 169m13,774s
sys 0m35,055s EDIT2: Updated example on the playground, old version Create the file #![recursion_limit="2048"]
#![type_length_limit="12457564"]
macro_rules! async_recursive {
(29, $inner:expr) => { async { async_recursive!(28, $inner) }.await };
(28, $inner:expr) => { async { async_recursive!(27, $inner) }.await };
(27, $inner:expr) => { async { async_recursive!(26, $inner) }.await };
(26, $inner:expr) => { async { async_recursive!(25, $inner) }.await };
(25, $inner:expr) => { async { async_recursive!(24, $inner) }.await };
(24, $inner:expr) => { async { async_recursive!(23, $inner) }.await };
(23, $inner:expr) => { async { async_recursive!(22, $inner) }.await };
(22, $inner:expr) => { async { async_recursive!(21, $inner) }.await };
(21, $inner:expr) => { async { async_recursive!(20, $inner) }.await };
(20, $inner:expr) => { async { async_recursive!(19, $inner) }.await };
(19, $inner:expr) => { async { async_recursive!(18, $inner) }.await };
(18, $inner:expr) => { async { async_recursive!(17, $inner) }.await };
(17, $inner:expr) => { async { async_recursive!(16, $inner) }.await };
(16, $inner:expr) => { async { async_recursive!(15, $inner) }.await };
(15, $inner:expr) => { async { async_recursive!(14, $inner) }.await };
(14, $inner:expr) => { async { async_recursive!(13, $inner) }.await };
(13, $inner:expr) => { async { async_recursive!(12, $inner) }.await };
(12, $inner:expr) => { async { async_recursive!(11, $inner) }.await };
(11, $inner:expr) => { async { async_recursive!(10, $inner) }.await };
(10, $inner:expr) => { async { async_recursive!(9, $inner) }.await };
(9, $inner:expr) => { async { async_recursive!(8, $inner) }.await };
(8, $inner:expr) => { async { async_recursive!(7, $inner) }.await };
(7, $inner:expr) => { async { async_recursive!(6, $inner) }.await };
(6, $inner:expr) => { async { async_recursive!(5, $inner) }.await };
(5, $inner:expr) => { async { async_recursive!(4, $inner) }.await };
(4, $inner:expr) => { async { async_recursive!(3, $inner) }.await };
(3, $inner:expr) => { async { async_recursive!(2, $inner) }.await };
(2, $inner:expr) => { async { async_recursive!(1, $inner) }.await };
(1, $inner:expr) => { async { async_recursive!(0, $inner) }.await };
(0, $inner:expr) => { async { $inner }.await };
}
async fn f() {
// 9: ~0.68s
// 10: ~0.83s
// 11: ~1.34s
// 12: ~2.68s
// 13: ~5.24s reached length limit -> #![type_length_limit="1228764"]
// 14: ~7.89s reached length limit -> #![type_length_limit="2457564"]
// 15: /playground/tools/entrypoint.sh: line 11: 7 Killed timeout --signal=KILL ${timeout} "$@"
async_recursive!(REPLACE_ME, println!("done"))
}
fn main() {
let _ = f();
} Then run: for n in $(seq 29); do sed "s/REPLACE_ME/$n/g" template.rs > main.rs && echo "== Level $n" && time rustc --edition 2018 main.rs; done Sample output, looks exponential:== Level 1
real 0m0,217s
user 0m0,149s
sys 0m0,038s
== Level 2
real 0m0,216s
user 0m0,169s
sys 0m0,039s
== Level 3
real 0m0,138s
user 0m0,132s
sys 0m0,017s
== Level 4
real 0m0,149s
user 0m0,129s
sys 0m0,029s
== Level 5
real 0m0,165s
user 0m0,127s
sys 0m0,047s
== Level 6
real 0m0,178s
user 0m0,165s
sys 0m0,022s
== Level 7
real 0m0,168s
user 0m0,141s
sys 0m0,035s
== Level 8
real 0m0,204s
user 0m0,178s
sys 0m0,038s
== Level 9
real 0m0,249s
user 0m0,214s
sys 0m0,046s
== Level 10
real 0m0,336s
user 0m0,319s
sys 0m0,028s
== Level 11
real 0m0,583s
user 0m0,575s
sys 0m0,016s
== Level 12
real 0m0,946s
user 0m0,927s
sys 0m0,030s
== Level 13
real 0m1,713s
user 0m1,674s
sys 0m0,036s
== Level 14
real 0m3,812s
user 0m3,667s
sys 0m0,079s
== Level 15
real 0m6,901s
user 0m6,807s
sys 0m0,055s
== Level 16
real 0m14,814s
user 0m14,599s
sys 0m0,156s
== Level 17
real 0m29,575s
user 0m28,952s
sys 0m0,312s
== Level 18
real 0m59,365s
user 0m58,339s
sys 0m0,800s
|
Hmm, there might actually be two separate issues here? The example I provided above behaves the same on rustc 1.43.0 to current (1.42.0 and before are even slower). But the repo provided by @nicholasbishop and my local project compile fast on <=1.45.* and really slow with 1.46 and later. |
Sounds like the first example might be #72408, then. |
I am able to somewhat limit it to argument parameters (being references! passing ownership has no effect). Create the file #![recursion_limit="2048"]
#![type_length_limit="112457564"]
pub async fn h0(v: &String, x: &u64) { println!("{} {}", v, x) }
pub async fn h1(v: &String, x: &u64) { h0(v, x).await }
pub async fn h2(v: &String, x: &u64) { h1(v, x).await }
pub async fn h3(v: &String, x: &u64) { h2(v, x).await }
pub async fn h4(v: &String, x: &u64) { h3(v, x).await }
pub async fn h5(v: &String, x: &u64) { h4(v, x).await }
pub async fn h6(v: &String, x: &u64) { h5(v, x).await }
pub async fn h7(v: &String, x: &u64) { h6(v, x).await }
pub async fn h8(v: &String, x: &u64) { h7(v, x).await }
pub async fn h9(v: &String, x: &u64) { h8(v, x).await }
pub async fn h10(v: &String, x: &u64) { h9(v, x).await }
pub async fn h11(v: &String, x: &u64) { h10(v, x).await }
pub async fn h12(v: &String, x: &u64) { h11(v, x).await }
pub async fn h13(v: &String, x: &u64) { h12(v, x).await }
pub async fn h14(v: &String, x: &u64) { h13(v, x).await }
pub async fn h15(v: &String, x: &u64) { h14(v, x).await }
pub async fn h16(v: &String, x: &u64) { h15(v, x).await }
pub async fn h17(v: &String, x: &u64) { h16(v, x).await }
pub async fn h18(v: &String, x: &u64) { h17(v, x).await }
pub async fn h19(v: &String, x: &u64) { h18(v, x).await }
macro_rules! async_recursive {
(29, $inner:expr) => { async { async_recursive!(28, $inner) }.await };
(28, $inner:expr) => { async { async_recursive!(27, $inner) }.await };
(27, $inner:expr) => { async { async_recursive!(26, $inner) }.await };
(26, $inner:expr) => { async { async_recursive!(25, $inner) }.await };
(25, $inner:expr) => { async { async_recursive!(24, $inner) }.await };
(24, $inner:expr) => { async { async_recursive!(23, $inner) }.await };
(23, $inner:expr) => { async { async_recursive!(22, $inner) }.await };
(22, $inner:expr) => { async { async_recursive!(21, $inner) }.await };
(21, $inner:expr) => { async { async_recursive!(20, $inner) }.await };
(20, $inner:expr) => { async { async_recursive!(19, $inner) }.await };
(19, $inner:expr) => { async { async_recursive!(18, $inner) }.await };
(18, $inner:expr) => { async { async_recursive!(17, $inner) }.await };
(17, $inner:expr) => { async { async_recursive!(16, $inner) }.await };
(16, $inner:expr) => { async { async_recursive!(15, $inner) }.await };
(15, $inner:expr) => { async { async_recursive!(14, $inner) }.await };
(14, $inner:expr) => { async { async_recursive!(13, $inner) }.await };
(13, $inner:expr) => { async { async_recursive!(12, $inner) }.await };
(12, $inner:expr) => { async { async_recursive!(11, $inner) }.await };
(11, $inner:expr) => { async { async_recursive!(10, $inner) }.await };
(10, $inner:expr) => { async { async_recursive!(9, $inner) }.await };
(9, $inner:expr) => { async { async_recursive!(8, $inner) }.await };
(8, $inner:expr) => { async { async_recursive!(7, $inner) }.await };
(7, $inner:expr) => { async { async_recursive!(6, $inner) }.await };
(6, $inner:expr) => { async { async_recursive!(5, $inner) }.await };
(5, $inner:expr) => { async { async_recursive!(4, $inner) }.await };
(4, $inner:expr) => { async { async_recursive!(3, $inner) }.await };
(3, $inner:expr) => { async { async_recursive!(2, $inner) }.await };
(2, $inner:expr) => { async { async_recursive!(1, $inner) }.await };
(1, $inner:expr) => { async { async_recursive!(0, $inner) }.await };
(0, $inner:expr) => { async { h19(&String::from("owo"), &0).await; $inner }.await };
}
async fn f() {
async_recursive!(REPLACE_ME, println!("hello"));
}
fn main() {
let _ = f();
}
The execute: for n in $(seq 15); do sed "s/REPLACE_ME/$n/g" template.rs > main.rs && echo && echo && echo && echo "== Level $n @ 1.45.0" && time rustc +1.45.0 --edition 2018 main.rs && echo "== Level $n @ 1.46.0" && time rustc --edition 2018 main.rs; done Which results in:
EDIT: The example provided by @nicholasbishop can be simplified further: use actix_web::{web, HttpResponse, Responder};
use bb8_postgres::PostgresConnectionManager;
use tokio_postgres::NoTls;
type Pool = bb8::Pool<PostgresConnectionManager<NoTls>>;
async fn handle_req_1(pool: &Pool) {
let conn = pool.get().await.unwrap();
conn.query_one("", &[]).await.unwrap();
}
async fn handle_req_final(pool: web::Data<Pool>) -> impl Responder {
handle_req_1(pool.get_ref()).await;
HttpResponse::Ok().finish()
}
pub fn app_config() {
let _ = web::post().to(handle_req_final);
} With dependencies pre-built, this takes about 0.5s with rustc 1.45.0 and 4.5s with rustc 1.46.0. |
I think @rustbot ping cleanup |
Hey Cleanup Crew ICE-breakers! This bug has been identified as a good cc @AminArria @camelid @chrissimpkins @contrun @DutchGhost @elshize @ethanboxx @h-michael @HallerPatrick @hdhoang @hellow554 @imtsuki @kanru @KarlK90 @LeSeulArtichaut @MAdrianMattocks @matheus-consoli @mental32 @nmccarty @Noah-Kennedy @pard68 @PeytonT @pierreN @Redblueflame @RobbieClarken @RobertoSnap @robjtede @SarthakSingh31 @senden9 @shekohex @sinato @spastorino @turboladen @woshilapin @yerke |
This comment has been hidden.
This comment has been hidden.
I think I have a related error. My lib compiled fine under 1.45, but
The code is here, master branch is failing |
Yeah, this might be related to #54540. (#54540 (comment)) |
Did my own bisection (4 times). Each time it identified the regression in nightly-2020-08-14. One time (probably a timing error/blurp?) the result was: searched nightlies: from nightly-2020-05-03 to nightly-2020-08-27 bisected with cargo-bisect-rustc v0.5.2Host triple: x86_64-unknown-linux-gnu cargo bisect-rustc --test-dir=. --start 2020-05-03 --end 2020-08-27 Three times the result was (the same as #54540 (comment)) : searched nightlies: from nightly-2020-05-03 to nightly-2020-08-27 bisected with cargo-bisect-rustc v0.5.2Host triple: x86_64-unknown-linux-gnu cargo bisect-rustc --test-dir=. --start 2020-05-03 --end 2020-08-27 bash scriptYou may need to tweak this, depending on your local machine. #!/bin/bash
set -e
rustc --version >> result.txt
echo "Test 1" >> result.txt
time timeout 7 rustc --edition 2018 src/main.rs
echo "Test 2" >> result.txt
time timeout 7 rustc --edition 2018 src/main.rs
echo "Test 3" >> result.txt
time timeout 7 rustc --edition 2018 src/main.rs rust code#![recursion_limit="2048"]
#![type_length_limit="112457564"]
pub async fn h0(v: &String, x: &u64) { println!("{} {}", v, x) }
pub async fn h1(v: &String, x: &u64) { h0(v, x).await }
pub async fn h2(v: &String, x: &u64) { h1(v, x).await }
pub async fn h3(v: &String, x: &u64) { h2(v, x).await }
pub async fn h4(v: &String, x: &u64) { h3(v, x).await }
pub async fn h5(v: &String, x: &u64) { h4(v, x).await }
pub async fn h6(v: &String, x: &u64) { h5(v, x).await }
pub async fn h7(v: &String, x: &u64) { h6(v, x).await }
pub async fn h8(v: &String, x: &u64) { h7(v, x).await }
pub async fn h9(v: &String, x: &u64) { h8(v, x).await }
pub async fn h10(v: &String, x: &u64) { h9(v, x).await }
pub async fn h11(v: &String, x: &u64) { h10(v, x).await }
pub async fn h12(v: &String, x: &u64) { h11(v, x).await }
pub async fn h13(v: &String, x: &u64) { h12(v, x).await }
pub async fn h14(v: &String, x: &u64) { h13(v, x).await }
pub async fn h15(v: &String, x: &u64) { h14(v, x).await }
pub async fn h16(v: &String, x: &u64) { h15(v, x).await }
pub async fn h17(v: &String, x: &u64) { h16(v, x).await }
pub async fn h18(v: &String, x: &u64) { h17(v, x).await }
pub async fn h19(v: &String, x: &u64) { h18(v, x).await }
macro_rules! async_recursive {
(29, $inner:expr) => { async { async_recursive!(28, $inner) }.await };
(28, $inner:expr) => { async { async_recursive!(27, $inner) }.await };
(27, $inner:expr) => { async { async_recursive!(26, $inner) }.await };
(26, $inner:expr) => { async { async_recursive!(25, $inner) }.await };
(25, $inner:expr) => { async { async_recursive!(24, $inner) }.await };
(24, $inner:expr) => { async { async_recursive!(23, $inner) }.await };
(23, $inner:expr) => { async { async_recursive!(22, $inner) }.await };
(22, $inner:expr) => { async { async_recursive!(21, $inner) }.await };
(21, $inner:expr) => { async { async_recursive!(20, $inner) }.await };
(20, $inner:expr) => { async { async_recursive!(19, $inner) }.await };
(19, $inner:expr) => { async { async_recursive!(18, $inner) }.await };
(18, $inner:expr) => { async { async_recursive!(17, $inner) }.await };
(17, $inner:expr) => { async { async_recursive!(16, $inner) }.await };
(16, $inner:expr) => { async { async_recursive!(15, $inner) }.await };
(15, $inner:expr) => { async { async_recursive!(14, $inner) }.await };
(14, $inner:expr) => { async { async_recursive!(13, $inner) }.await };
(13, $inner:expr) => { async { async_recursive!(12, $inner) }.await };
(12, $inner:expr) => { async { async_recursive!(11, $inner) }.await };
(11, $inner:expr) => { async { async_recursive!(10, $inner) }.await };
(10, $inner:expr) => { async { async_recursive!(9, $inner) }.await };
(9, $inner:expr) => { async { async_recursive!(8, $inner) }.await };
(8, $inner:expr) => { async { async_recursive!(7, $inner) }.await };
(7, $inner:expr) => { async { async_recursive!(6, $inner) }.await };
(6, $inner:expr) => { async { async_recursive!(5, $inner) }.await };
(5, $inner:expr) => { async { async_recursive!(4, $inner) }.await };
(4, $inner:expr) => { async { async_recursive!(3, $inner) }.await };
(3, $inner:expr) => { async { async_recursive!(2, $inner) }.await };
(2, $inner:expr) => { async { async_recursive!(1, $inner) }.await };
(1, $inner:expr) => { async { async_recursive!(0, $inner) }.await };
(0, $inner:expr) => { async { h19(&String::from("owo"), &0).await; $inner }.await };
}
async fn f() {
async_recursive!(12, println!("hello"));
async_recursive!(12, println!("hello"));
async_recursive!(12, println!("hello"));
}
fn main() {
let _ = f();
}
EDIT: I also ran the bisection on the original example of @nicholasbishop (which either compiles in finite time with 1.45.x - ~22s for me - or takes forever with 1.46.0), with the same result: searched nightlies: from nightly-2020-05-03 to nightly-2020-08-27 bisected with cargo-bisect-rustc v0.5.2Host triple: x86_64-unknown-linux-gnu cargo bisect-rustc --test-dir=. --start 2020-05-03 --end 2020-08-27 bash scriptYou may need to tweak this, depending on your local machine. #!/bin/bash
set -e
cargo clean
time timeout 35 cargo build |
I bisected here algesten/hreq#11 It pointed to #0a49057 Regression in 0a49057 searched nightlies: from nightly-2020-08-12 to nightly-2020-08-15 bisected with cargo-bisect-rustc v0.5.2Host triple: x86_64-apple-darwin cargo bisect-rustc --start=2020-08-12 --end=2020-08-15 |
the example by @udoprog is really interesting :O will look into that, even if it may not have the same underlying cause as the other regressions here it is probably related |
Yeah. The hang is caused by spinning for a long time by formatting a *: Removing formatting for the type results in:
|
that's probably not related to the slowdown then Do you mind opening a new issue for that? I think we may want to fix this by somehow guarding against this in type printing |
But why would that code produce such a large future in the first place? It's not like it's multiplying the number of futures at each step; there's no obvious reason for what is presumably exponential growth in the size. |
Here is the trace for @pnkfelix actix_web example if that's helpful. |
That function is rust/compiler/rustc_mir/src/monomorphize/collector.rs Lines 339 to 340 in 0a2a68e |
In my case the build spends an hour in |
I'm guessing from @udoprog 's note that their example "Also struggles to compile on 1.45.0, initially by hitting type length limits. And when bumped it takes an increasingly longer time to compile the more lines are added" is a hint that it is not the same root cause, since the bug I believe we are addressing here is one where 1.45.0 was relatively speedy, right? (Of course we are always interested in understanding cases where the compiler is slow, even if they are not regressions. But I would prefer those to be tracked in separate tickets from outright regression like this one.) Update: oops, should have refreshed the page before posting; I see others have independently reached a similar conclusion. |
I now confirm that the compile-time regression for my test is resolved by PR #78410 (which includes a revert of PR #75443). |
revert rust-lang#75443, update mir validator This PR reverts rust-lang#75443 to fix rust-lang#75992 and instead uses rust-lang#75419 to fix rust-lang#75313. Adapts rust-lang#75419 to correctly deal with unevaluated constants as otherwise some `feature(const_evaluatable_checked)` tests would ICE. Note that rust-lang#72793 was also fixed by rust-lang#75443, but as that issue only concerns `feature(type_alias_impl_trait)` I deleted that test case for now and would reopen that issue. rust-lang#75443 may have also allowed some other code to now successfully compile which would make this revert a breaking change after 2 stable versions, but I hope that this is a purely theoretical concern. See https://rust-lang.zulipchat.com/#narrow/stream/182449-t-compiler.2Fhelp/topic/generator.20upvars/near/214617274 for more reasoning about this. r? `@nikomatsakis` `@eddyb` `@RalfJung`
Has this been fixed? Pretty new to Rust in general but I've hit a similar issue using a futures Stream from mongodb. If I move the code into the main function it will compile, however if I move that code into a service that sits behind an actix endpoint the compile will hang seemingly indefinitely. Update: @Davester47 FYI I just tested with the beta build and it seems to be fixed for me with mongo - hopefully it is for you too |
@Adam-Reeves I just tested beta and it works! Nightly however still hangs, and I think that the fix missed the latest nightly. It will probably work tomorrow. Thanks everyone for helping to get this fixed. Update: my project now builds with nightly. |
Did anyone ever manage to come up with a minimal, self-contained reproduction for this? |
I think #75992 (comment) is the smallest anyone got it. |
Good to know, although I wonder if it's premature to close this issue if we don't still have a self-contained repro. Shall we reopen? |
I'd be inclined to close it, I doubt we'll ever follow-up -- if it comes up again, we can try to minimize then. |
I upgraded from 1.45 to 1.46 today and a crate I'm working on seems to hang forever while compiling. I've attempted to create a somewhat minimal test case. For convenience here's a repo that includes the code below: https://github.com/nicholasbishop/rust146hang
Cargo.toml:
src/lib.rs:
I've found that the slowness seems tied to the depth of the async call chain. In the example code I have a long call chain:
handle_req_1
is called byhandle_req_2
is called byhandle_req_3
, etc. Here's the compilation timing I've observed when changinghandle_req_final
to directly call one of thehandle_req_N
functions:These times only include building the rust146hang lib, not the dependencies.
(Caveat: unscientific timings, these are not averaged over multiple
runs or anything.)