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

tokio: distinguish LocalSet::enter() with being polled #6016

Merged
merged 18 commits into from
Oct 15, 2023

Conversation

inq
Copy link
Contributor

@inq inq commented Sep 19, 2023

Motivation

This fixes: #5020

Solution

If we enter a LocalSet, then the LocalEnterGuard sets CURRENT with it. But the CURRENT is being used with both of these two situations:

  1. task::spawn_local()
  2. being polled
    But as we know, LocalEnterGuard should be related only with (1). When we schedule it, we should check if it is being polled, or just being entered. So I added a boolean variable to distinguish these situations.

@Darksonn Darksonn added A-tokio Area: The main tokio crate M-task Module: tokio/task labels Sep 19, 2023
@inq inq changed the title tokio: distinguish enter() with being polled tokio: distinguish LocalSet::enter() with being polled Sep 19, 2023
@hawkw hawkw self-requested a review September 19, 2023 16:10
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

Overall, this change looks good to me --- thanks for adding a test reproducing the bug!

I had some minor suggestions for potentially simplifying some aspects of the implementation a bit. Let me know what you think!

tokio/tests/task_local_set.rs Show resolved Hide resolved
Comment on lines 709 to 729
let res = CURRENT.try_with(|LocalData { ctx, entered, .. }| {
struct Reset<'a> {
ctx_ref: &'a RcCell<Context>,
val: Option<Rc<Context>>,
entered_ref: &'a Cell<bool>,
ctx_val: Option<Rc<Context>>,
entered_val: bool,
}
impl<'a> Drop for Reset<'a> {
fn drop(&mut self) {
self.ctx_ref.replace(self.val.take());
self.ctx_ref.set(self.ctx_val.take());
self.entered_ref.set(self.entered_val)
}
}
let old = ctx.replace(Some(self.context.clone()));
let ctx_old = ctx.replace(Some(self.context.clone()));
let entered_old = entered.replace(false);

let _reset = Reset {
ctx_ref: ctx,
val: old,
entered_ref: entered,
ctx_val: ctx_old,
entered_val: entered_old,
Copy link
Member

Choose a reason for hiding this comment

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

take it or leave it: the Reset code got substantially more complex in this change, and it's used in two different places. now that it's no longer fairly trivial, what do you think about moving this out of the with and with_if_possible functions, so that we can use one implementation in both places. Something like:

impl LocalData {
    fn enter(&self, context: Rc<Context>) -> Reset<'_> {
        let ctx_val = self.ctx.replace(Some(context));
        let entered_val = self.entered.replace(false);

        Reset {
            ctx_ref: self.ctx,
            entered_ref: self.entered,
            ctx_val: ctx_old,
            entered_val: entered_old,
        }
    }
}

struct Reset<'a> {
    ctx_ref: &'a RcCell<Context>,
    entered_ref: &'a Cell<bool>,
    ctx_val: Option<Rc<Context>>,
    entered_val: bool,
}

impl<'a> Drop for Reset<'a> {
    fn drop(&mut self) {
        self.ctx_ref.set(self.ctx_val.take());
        self.entered_ref.set(self.entered_val)
    }
}

and then changing with and with_if_possible to just call

let _reset = local_data.enter(self.context.clone());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! I've moved out, and renamed it to more verbose name.

Comment on lines 679 to 680
ctx_ref: &'a RcCell<Context>,
val: Option<Rc<Context>>,
entered_ref: &'a Cell<bool>,
Copy link
Member

Choose a reason for hiding this comment

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

nit, not a big deal: both ctx_ref and entered_ref reference fields of the LocalData. we could make this struct a word smaller by changing these to a single &'a LocalData reference, and just using that to access both ctx and entered.

Copy link
Contributor Author

@inq inq Sep 19, 2023

Choose a reason for hiding this comment

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

It's a good idea. I've changed it!

pub struct LocalEnterGuard(Option<Rc<Context>>);
pub struct LocalEnterGuard {
ctx: Option<Rc<Context>>,
entered: bool,
Copy link
Member

Choose a reason for hiding this comment

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

can we add a comment here describing how entered is used and what it represents? thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added! Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

Sorry if this is overly nitpicky, but I think this comment could maybe be improved a bit --- it would be nice if it explained why we need to differentiate between enter and polling the LocalSet, and what behavior is controlled based on that. I don't think that's currelty all that clear from reading the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops, sorry! I've corrected it!

@inq
Copy link
Contributor Author

inq commented Sep 19, 2023

@hawkw Thank you for providing an awesome review!

tokio/src/task/local.rs Outdated Show resolved Hide resolved
tokio/src/task/local.rs Show resolved Hide resolved
tokio/src/task/local.rs Show resolved Hide resolved
tokio/src/task/local.rs Show resolved Hide resolved
tokio/src/task/local.rs Outdated Show resolved Hide resolved
@hawkw
Copy link
Member

hawkw commented Sep 19, 2023

@hawkw Thank you for providing an awesome review!

@inq My pleasure, thanks for contributing the fix! :)

@inq
Copy link
Contributor Author

inq commented Sep 25, 2023

@hawkw Hello! I am sorry but can we proceed this?

@hawkw
Copy link
Member

hawkw commented Sep 28, 2023

@hawkw Hello! I am sorry but can we proceed this?

Hi, sorry for not getting back to you sooner --- I'm taking another look now!

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

Overall, this looks good to me. I think it would be nice to document the behavior with the entered flag and when the LocalSet future is woken, but I feel good about the implementation and tests, and would be happy to merge this PR. Thank you!

pub struct LocalEnterGuard(Option<Rc<Context>>);
pub struct LocalEnterGuard {
ctx: Option<Rc<Context>>,
entered: bool,
Copy link
Member

Choose a reason for hiding this comment

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

Sorry if this is overly nitpicky, but I think this comment could maybe be improved a bit --- it would be nice if it explained why we need to differentiate between enter and polling the LocalSet, and what behavior is controlled based on that. I don't think that's currelty all that clear from reading the code.

tokio/src/task/local.rs Show resolved Hide resolved
tokio/tests/task_local_set.rs Outdated Show resolved Hide resolved
tokio/src/task/local.rs Outdated Show resolved Hide resolved
@inq
Copy link
Contributor Author

inq commented Oct 4, 2023

@hawkw I am really sorry for delayed works. Your kind correction helped me a lot! Thank you very much!

Comment on lines 292 to 301
/// Should be called except when we call `LocalSet::enter`.
/// Especially when we poll a LocalSet.
#[must_use = "dropping this guard will reset the entered state"]
fn enter(&self, ctx: Rc<Context>) -> LocalDataEnterGuard<'_> {
let ctx = self.ctx.replace(Some(ctx));
let entered = self.entered.replace(false);
LocalDataEnterGuard {
local_data_ref: self,
ctx,
entered,
Copy link
Contributor

Choose a reason for hiding this comment

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

I was reading over this code, and I'm confused about this. Why does calling enter set entered to false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's because of the name of the user-facing level functions. So this LocalData::enter should be called when we poll, and the value entered should be set only if user called the LocalSer::enter, which is public.
I agree that it's confused, but could not find suitable naming..

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we rename the field to something like wake_on_spawn_local?

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @Darksonn, the entered field could be renamed to something clearer.

Copy link

Choose a reason for hiding this comment

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

Agreed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you very much! I've renamed it!
But... wake_on_spawn_local vs wake_on_schedule.. which would be better?

Copy link
Contributor

Choose a reason for hiding this comment

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

wake_on_schedule also makes sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! I've renamed it to wake_on_schdule!

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 f3ad6cf into tokio-rs:master Oct 15, 2023
69 checks passed
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-task Module: tokio/task
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LocalEnterGuard would cause current thread runtime to become stuck
4 participants