Skip to content

Conversation

@Twey
Copy link
Contributor

@Twey Twey commented May 30, 2025

Motivation

We would like to be able to create a local runtime from the tokio::main attribute in order to be able to use spawn_local in functions that rely on tokio::main or tokio::test.

Solution

Add a local flavor to tokio::main which behaves like current-thread, but calls build_local(Default::default()) instead of build().

@Darksonn Darksonn added A-tokio-macros Area: The tokio-macros crate M-runtime Module: tokio/runtime labels May 31, 2025
@Darksonn Darksonn requested a review from carllerche May 31, 2025 11:39
@Darksonn
Copy link
Contributor

Darksonn commented Jun 3, 2025

Can you please make sure that CI passes? There's a rustfmt failure.

@Twey
Copy link
Contributor Author

Twey commented Jun 3, 2025

Thanks for taking the time to review @ADD-SP @Darksonn! I think I've addressed your comments. Would you mind taking another look?

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.

Sorry about the delay in review.

}
(F::CurrentThread, None) => None,
(F::CurrentThread | F::Local, None) => None,
(F::Threaded, worker_threads) if self.rt_multi_thread_available => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add logic to rt_multi_thread_available to detect whether --cfg tokio_unstable is set and emit a hard error if it is not.

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'm not sure I understand this comment. Did you mean to add the logic to RuntimeFlavor::from_str?

Copy link
Contributor

Choose a reason for hiding this comment

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

The error should be similar to this error:

let msg = if self.flavor.is_none() {
"The default runtime flavor is `multi_thread`, but the `rt-multi-thread` feature is disabled."
} else {
"The runtime flavor `multi_thread` requires the `rt-multi-thread` feature."
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe you meant a0ca77e; if I've misinterpreted please feel free to suggest changes :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I just saw your comment. Is 4363203 more what you had in mind?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think both are okay.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is harder than it appears because RUSTFLAGS is not passed to proc macros. I guess we have to emit a cfg check, so it actually can't live in either of these places.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

44a31b1 seems to work in my local tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't that also trigger other unrelated errors when it fails?

What we do today is essentially define the main macro two times to pass different values to the rt_multi_thread boolean, and have lib.rs do this:

#[cfg(feature = "rt-multi-thread")]
pub use tokio_macros::main;
#[cfg(not(feature = "rt-multi-thread"))]
pub use tokio_macros::main_rt as main;

This way, the macro knows based on the cfgs that apply in the main Tokio crate. We could use the same approach and have four macros.

Copy link
Contributor Author

@Twey Twey Jul 7, 2025

Choose a reason for hiding this comment

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

The quadratic explosion is a little bit scary to me. We can avoid emitting additional code if checks fail like 213598d.

@carllerche
Copy link
Member

This looks fine to me. I'm happy to merge this when @Darksonn is. Are there docs? My main concern is explaining to users the difference between flavor local and flavor current_thread and how to pick between them.

@Twey
Copy link
Contributor Author

Twey commented Jun 21, 2025

Is there a reason to use current_thread over local when it's available?

@Darksonn
Copy link
Contributor

@Twey I'm pretty reluctant to change the default runtime. I do not want a repeat of #4142 where we had to revert something.

@Twey
Copy link
Contributor Author

Twey commented Jun 23, 2025

I'm happy enough to not change the default runtime — but I'm not really clear on what I should write in the documentation about when to choose current_thread over local.

@Twey
Copy link
Contributor Author

Twey commented Jun 23, 2025

I'm not certain I've done the right thing but I've done my best :) Please feel free to suggest further changes if I didn't understand something correctly.

///
/// Equivalent code not using `#[tokio::main]`
///
/// ```ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this work?

Suggested change
/// ```ignore
/// ```
/// #![cfg(tokio_unstable)]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it will be executed. We could use text but then we lose Rust highlighting.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we can execute it, then I think we should. Why can't we?

Copy link
Contributor Author

@Twey Twey Jun 24, 2025

Choose a reason for hiding this comment

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

We can do b040de5.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can't execute it because the LocalExecutor doesn't exist if we don't have tokio_unstable, which we don't usually have in tests. But maybe it's clearer this way anyway.

Copy link
Contributor

Choose a reason for hiding this comment

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

What if we add a cfg(tokio_unstable) to the test so it only actually runs under tokio_unstable? I believe we run tests both with and without the flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, that's b040de5 :)

@Twey Twey force-pushed the local-macro branch 3 times, most recently from b1f3a19 to 44a31b1 Compare June 24, 2025 13:19
@jobafr
Copy link

jobafr commented Jul 10, 2025

Hi, I found this PR because I was trying to figure out why spawn_local would not work when using the current-thread runtime from tokio::main. Maybe you could add something to the docs of spawn_local to indicate that this is going to work when using a main with #[tokio::main(flavor = "local")]?

Maybe something like

Spawns a !Send future on the current [LocalSet] or [LocalRuntime].

This is possible when either using one of these types explicitly, or by opting to use RuntimeFlavor::Local for the calling main function like so: #[tokio::main(flavor = "local")].

@Twey
Copy link
Contributor Author

Twey commented Jul 10, 2025

Maybe you could add something to the docs of spawn_local

In general it doesn't seem like we refer to the macros in the core API docs. I don't have anything against it myself, but I'll let the others weigh in on whether that's something we want to start doing. :)

@Darksonn
Copy link
Contributor

Referring to the macros in core API docs is perfectly fine.

@Twey
Copy link
Contributor Author

Twey commented Jul 10, 2025

Okay! 8c3d838

I slightly reworded it @jobafr to avoid mention of the internal RuntimeFlavor::Local enum, and to be explicit (again) that it will only work with tokio_unstable. I hope that's okay.

Comment on lines 471 to 474
#[allow(unreachable_code)]
{
panic!("fell through checks")
}
Copy link
Member

Choose a reason for hiding this comment

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

Is this code needed? (since this is after the return statement, I think it will never be executed.)

Copy link
Member

Choose a reason for hiding this comment

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

Or, does this work as a safeguard for future macro changes?

Copy link
Contributor Author

@Twey Twey Jul 18, 2025

Choose a reason for hiding this comment

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

If the checks fail, the function body (which contains ill-formed code) won't be called. This trailer exists to provide a value of the function return type in the case that we aren't returning the real return value earlier so that we don't surface an unhelpful type mismatch error to the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I can tell, the addition of this panic is the cause of the worse error messages seen in tests-build/tests/fail/macros_type_mismatch.stderr. Having reasonably good error messages when used incorrectly is a goal of the macro.

Can you try annotating the fall-through case with #[cfg(not(tokio_unstable))] and see if that avoids the regression on the error messages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 9933509.

@Twey Twey requested a review from Darksonn July 25, 2025 12:06
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 4b96af6 into tokio-rs:master Jul 28, 2025
86 checks passed
martin-g added a commit to martin-g/tokio that referenced this pull request Sep 9, 2025
tokio-rs#7375 added the Local runtime
flavour but forgot to update the error message when an unknown value is
passed to from_str()
martin-g added a commit to martin-g/tokio that referenced this pull request Sep 9, 2025
tokio-rs#7375 added the Local runtime
flavour but forgot to update the error message when an unknown value is
passed to from_str()
This was referenced Oct 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-tokio-macros Area: The tokio-macros crate M-runtime Module: tokio/runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants