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

having a potential deadlock in code #1

Open
zylthinking opened this issue Aug 25, 2021 · 3 comments
Open

having a potential deadlock in code #1

zylthinking opened this issue Aug 25, 2021 · 3 comments

Comments

@zylthinking
Copy link

That can be produced when main drop reactor early than in threads. e.g

reactor.lock().map(|mut r: MutexGuard<Box<Reactor>>| r.wake(id)).unwrap(); 
thread::sleep(Duration::from_secs(6)); 
drop(reactor);

and

block_on(mainfut);
drop(reactor);
thread::sleep(Duration::from_secs(12));
@cfsamson
Copy link
Owner

As long as there are tasks queued, dopping the reactor should only decrease the refcount since it's wrapped in an Arc and each task needs a copy of the reactor to work. I'm not sure, I understand the deadlock scenario fully here. Could you provide an example I can test using the final example, for example using the playground (although I guess it will time out with the sleep intervals in your code above I can copy it and run in locally): https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=91a5cdf856530eecfb46345a36fb5bbb

@zylthinking
Copy link
Author

zylthinking commented Aug 27, 2021 via email

@cfsamson
Copy link
Owner

cfsamson commented Aug 29, 2021

Thanks. Good catch!

I think the major issue is that I use drop to shut down the reactor, but can't guarantee that the shutdown signal isn't sent from one of the child threads (as you showed when the refcount reaches 1 in one of the child threads instead of the main thread as intended).

The issue is probably a bit contrived since I fire off a seperate thread to handle each event for this example as we don't have any real I/O and use sleeping threads to simulate the waiting. In a real Reactor you wouldn't fire off threads that way and rely on sleep for wakeup. One solution is to treat the timer threads as fire-and-forget since they're only created to be able to simulate waiting for an external event. We don't have to join them.

If we want to keep with best practices and join all spawned threads (even the ones that we create only for simulation). I think the cleanest solution is to not rely on Drop for cleanly shutting down the Reactor but instead provide an instance method for it so if we remove the Drop impl and instead add an instance method called shutdown on Reactor:

    fn shutdown(&mut self) {
        println!("really drop, sending quit");
        self.dispatcher.send(Event::Close).unwrap();
        println!("waiting for executor thread done");
        self.handle.take().map(|h| h.join().unwrap()).unwrap();
        println!("executor thread ended");
    }

And in "main" we need to call this instead of relying on drop to shutdown the reactor:

    println!("drop 1");
    reactor.lock().map(|mut r| r.shutdown()).unwrap();
    println!("just wait long to observe 2 threads will blocked forever");
    thread::sleep(Duration::from_secs(1000));

Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=67550c19e85246827cdb8422b63ceb44

What do you think? Do you think it's better to rewrite Drop or just treat the timer threads as fire-and-forget by not joining them?

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

No branches or pull requests

2 participants