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

block_in_place with nested block_on allows async drop #5843

Closed
nappa85 opened this issue Jun 30, 2023 · 5 comments
Closed

block_in_place with nested block_on allows async drop #5843

nappa85 opened this issue Jun 30, 2023 · 5 comments
Labels
A-tokio Area: The main tokio crate C-bug Category: This is a bug. M-runtime Module: tokio/runtime

Comments

@nappa85
Copy link

nappa85 commented Jun 30, 2023

Version
All versions since 1.4 (prior versions doesn't have Handle::block_on but probably works here too with minimal adaptation.

Description
Using tokio::task::block_in_place with a nested tokio::runtime::Handle::block_on allows using async code in sync context, e.g. on Drop implementation.

E.g.

#[derive(Debug)]
struct Foo;

impl Foo {
    async fn bar(&self) {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        println!("works!");
    }
}

impl Drop for Foo {
    fn drop(&mut self) {
        tokio::runtime::Handle::current().block_on(async {
            self.bar().await;
        });
    }
}

#[tokio::main]
async fn main() {
    let f = Foo {};
    println!("{f:?}");
}

this code panics with

thread 'main' panicked at 'Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.'

But simply wrapping block_on in a block_in_place everything works:

#[derive(Debug)]
struct Foo;

impl Foo {
    async fn bar(&self) {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        println!("works!");
    }
}

impl Drop for Foo {
    fn drop(&mut self) {
        tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(async {
                self.bar().await;
            });
        });
    }
}

#[tokio::main]
async fn main() {
    let f = Foo {};
    println!("{f:?}");
}

Is this the desired behavior?

@nappa85 nappa85 added A-tokio Area: The main tokio crate C-bug Category: This is a bug. labels Jun 30, 2023
@Darksonn Darksonn added the M-runtime Module: tokio/runtime label Jul 1, 2023
@Darksonn
Copy link
Contributor

Darksonn commented Jul 1, 2023

Yes, that's what block_in_place does. But keep in mind that it doesn't always work, e.g. it does not work in a current-thread runtime.

@nappa85
Copy link
Author

nappa85 commented Jul 2, 2023

Thanks for the reply.

Yes, the behavior in a current-thread runtime was clear from block_in_place documentation.

What is smelly to me is that we are able to "re-enter" the async runtime from any place, seems strange it isn't abused e.g. to obtain async drop.

@nappa85 nappa85 closed this as completed Jul 2, 2023
@nappa85
Copy link
Author

nappa85 commented Jul 3, 2023

@Darksonn sorry for the tag but I need a little clarification.

There are two factors here:

  • block_in_place informs the executor that the task is going to block, avoiding possible starvation
  • block_on reenters the async context and, since it doesn't asks for the future to be Send, it runs locally

but how does those two works together? Is the await time in the current thread "spoiled" because queued tasks were already moved to another worker and, because of block_in_place, no other task will be added to current thread?

@Darksonn
Copy link
Contributor

Darksonn commented Jul 3, 2023

When you call block_in_place, the runtime makes it so that the current thread is no longer a worker thread for the runtime, and spawns a new worker thread to replace it. The current thread will never execute any other tasks again.

At that point, the thread behaves like any other thread outside of the runtime. You can call block_on, and it has the same effect as if you call block_on from a new thread outside the runtime. No other tasks will be scheduled on the current thread.

(Technically, if the call to block_in_place is very quick and returns before the new worker thread has been spawned, then the current thread continues being a worker thread as-if nothing happened.)

@dpc
Copy link
Contributor

dpc commented Apr 5, 2024

This also sometimes seems to hang on Runtime drop. If you gdb the hanged process, you will find all remaining runtime threads block_on-ed, waiting for a wakeup but no thread that would actually poll events.

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 C-bug Category: This is a bug. M-runtime Module: tokio/runtime
Projects
None yet
Development

No branches or pull requests

3 participants