seanmonstar / warp

A super-easy, composable, web server framework for warp speeds.
https://seanmonstar.com/post/176530511587/warp
MIT License
9.56k stars 718 forks source link

Facing 'stack-overflow' issue when the number of filters are increased #811

Open iamsiddhant05 opened 3 years ago

iamsiddhant05 commented 3 years ago

Version 0.3

Platform MacOS Big Sur

Description

I'm facing an issue and not sure if it's a bug. This is my main route:

    let route = warp::path("health").and(warp::get()).map(|| "ok!");

    let all_app_routes = warp::path("v1")
        .and(
            route
                .or(routes::type_one(app_state.clone())) // 12 filters
                .or(routes::type_two(app_state.clone())) // 8 filters
                .or(routes::type_three(app_state.clone())) // 2
                .or(routes::type_four(app_state.clone())) // 4
                .or(routes::type_five(app_state.clone())), // 5
        )
        .recover(response::handle_rejection)
        .with(log);

When i run the above routes which has 5 modules with the number of filters(API) in each given, it gives me the following error:

thread 'tokio-runtime-worker' has overflowed its stack
fatal runtime error: stack overflow
zsh: abort      cargo run -p rest-server

As soon as i reduce/remove any one of the module the server starts working properly. Is there a limit to the number of stacking filters or the structure being using by me is causing this issue. I would love to provide more info you need.

iamsiddhant05 commented 3 years ago

An update: If i split the first module routes into 2 or with less filter it works again.

let route = warp::path("health").and(warp::get()).map(|| "ok!");

    let all_app_routes = warp::path("v1")
        .and(
            route
                .or(routes::type_one_part_one(app_state.clone())) // 9 filters
                .or(routes::type_one_part_two(app_state.clone())) // 3 filters
                .or(routes::type_two(app_state.clone())) // 8 filters
                .or(routes::type_three(app_state.clone())) // 2
                .or(routes::type_four(app_state.clone())) // 4
                .or(routes::type_five(app_state.clone())), // 5
        )
        .recover(response::handle_rejection)
        .with(log);

I'll be going ahead with this solution right now, but would very much like to know what is going wrong here.

frederikbosch commented 3 years ago

I have exactly the same problem at the moment, but I found another solution. My problem was solved by adding a .boxed() after all my .or() functions defining the routes, although I do not exactly understand why. I am really interested to find out the best solution.

nui commented 3 years ago

I have been bitten by this error for a long time. This error happen on debug build only in my case.

I have exactly the same problem at the moment, but I found another solution. My problem was solved by adding a .boxed() after all my .or() functions defining the routes, although I do not exactly understand why. I am really interested to find out the best solution.

You save my day 🙏. I ended up with this macro to add .boxed() after all my .or() when compiled using debug profile.

#[cfg(debug_assertions)]
macro_rules! compose {
    ($x:expr $(,)?) => (
        $x
    );
    ($x0:expr, $($x:expr),+ $(,)?) => (
        $x0$(.or($x))+.boxed()
    );
}
#[cfg(not(debug_assertions))]
macro_rules! compose {
    ($x:expr $(,)?) => (
        $x
    );
    ($x0:expr, $($x:expr),+ $(,)?) => (
        $x0$(.or($x))+
    );
}

// This code
let routes = compose!(a, b, c, d);
// Expand to this on debug build
let routes = a.or(b).or(c).or(d).boxed();
// And expand to this on release build
let routes = a.or(b).or(c).or(d);

This works fine for me so I think it worth to share here.

frederikbosch commented 3 years ago

Glad to help, and thanks for the share! While my solutions worked when I wrote it, when my app grew it started failing again. Now I have a .boxed() after some of my .or() calls. So in your example I now have random boxed() included in the compositing of routes: a.or(b).boxed().or(c).or(d).boxed().

This error happen on debug build only in my case.

Do you have any idea why this is the case? Are you sure this is not the case in release builds?

nui commented 3 years ago

Are you sure this is not the case in release builds?

I have never seen this error on SIT, UAT, and production deployment so I pretty sure this is debug build issue.

Do you have any idea why this is the case?

Release builds inline most of function calls so you get lower call stack depth than debug build.

Now I have a .boxed() after some of my .or() calls.

That sounds like a macro job. This is my updated version of macro if you are interested.

#[cfg(debug_assertions)]
macro_rules! compose {
    ($x:expr $(,)?) => (
        $x.boxed()
    );
    ($x0:expr, $($x:expr),+ $(,)?) => (
        $x0.boxed()$(.or($x.boxed()))+.boxed()
    );
}
#[cfg(not(debug_assertions))]
macro_rules! compose {
    ($x:expr $(,)?) => (
        $x
    );
    ($x0:expr, $($x:expr),+ $(,)?) => (
        $x0$(.or($x))+
    );
}

// This code
let routes = compose!(a, b, c, d);
// Expand to this on debug build
let routes = a.boxed().or(b.boxed()).or(c.boxed()).or(d.boxed()).boxed();
// And expand to this on release build
let routes = a.or(b).or(c).or(d);
Onefox commented 3 years ago

I started getting Overflow evaluating the requirement errors E0275 after I updated from rust1.52 to rust1.53.

When I remove some of my dispatching or routes then its compiling just fine. Also when I am using cargo build it works but with cargo build --release it fails with that error after the update to 1.53

After adding some .boxed() to some of the routes its working again...

Onefox commented 2 years ago

So by now my application is at around 55 routes in the main or filter with all having a .boxed() added to them.

Now with running the debug cargo run its starting but once I hit any endpoint I am getting the:

thread 'tokio-runtime-worker' has overflowed its stack
fatal runtime error: stack overflow
Abort trap: 6 

error and it fails.

Its still working if I use cargo --release run.

But I fear that once I add a couple more routes it will come crashing down then as well.

On a side note I am having heavy performance issues that resolve themselves when I remove a more and more routes from the filter, but just having less routes isn't really a solution...

Does someone has a clue on whats going on or how to improve the situation? I am using rust 1.58 with warp 0.3 feature compression

apiraino commented 2 years ago

.boxed() routes will only take us this far and seems to push the problem away for a while. Another workaround is increasing the minimum stack size.

@Onefox just my .2 cents, but by the way it looks, I think the core of the issue is the Filter routing system and how Warp is nesting types and resolves types at compile time (hand-wavy explaination, I don't have enough context for a good diagnose). However, from what I can see this is just the way Warp works and I'm not sure there's something that can be done about it: happy to receive insights and comments, though. The Filter system of Warp also reacts to changes on the Rust compiler (example: a recent regression on 1.57, then mostly solved in 1.58/1.59, made compiling a Warp project pretty much slow).

nocker01 commented 2 years ago

I've experienced this same issue only when running in debug mode. Thanks @frederikbosch and @nui , I've .boxed() my routes and the issue no longer occurs.

nappa85 commented 2 years ago

Same problem here, instead of boxing filters I've solved using RUST_MIN_STACK env var when launching in debug (on release mode I don't have any problem). E.g. RUST_MIN_STACK=4000000 cargo run