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

linux: Feature gate async runtime, allowing opting between Tokio or smol #27

Merged
merged 10 commits into from
Nov 4, 2022

Conversation

jxs
Copy link
Contributor

@jxs jxs commented Sep 28, 2022

Following #21 and confirming @dignifiedquire's #21 (comment), we still can't define features per target.

But it seems we can define features for dependencies, and then dependencies can be defined per target.
With that we can apply the similar technique rtnetlink uses to support both runtimes, with the difference that in our case as smol_socket is the default (to maintain backwards compatibility) we invert the feature combination and if both are enabled tokio takes priority.

Only downside that comes to mind with this approach is if someone uses if-watch with default-features = false crate compilation breaks (which is also the reason this change is technically a breaking change). We can use compile_error! to give a better compilation error.

CC @thomaseizinger

@dignifiedquire
Copy link

nice solution 👍

src/linux.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated
Comment on lines 13 to 14
tokio = ["rtnetlink/tokio_socket"]
smol_socket = ["rtnetlink/smol_socket"]

Choose a reason for hiding this comment

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

So what happens if I am on windows and activate the tokio feature 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.

So, I tried compiling with default features, tokio and --no-default-features.
All compile normally and produce the same Cargo.lock.
Btw should we name the features as rtnetlink names them or generify to smol and tokio ?

Choose a reason for hiding this comment

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

That is surprising to me. I would have expected cargo to choke on a feature that points to a dependency that is not present in the manifest for this target.

Choose a reason for hiding this comment

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

I'd prefer if we just name them after the executor and drop the _socket.

by making smol the default `IfWatcher` and introducing `TokioIfWatcher`.
Copy link

@thomaseizinger thomaseizinger left a comment

Choose a reason for hiding this comment

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

Thanks! Couple of comments to ponder over :)

Is my understanding correct that rtnetlinks handling of sockets with this try_spawn_blocking stuff doesn't actually affect us because we are not using those APIs?

That is great news because I think it means we can actually fully encapsulate the way we enable executors without any upstream changes!

src/linux.rs Outdated Show resolved Hide resolved
Cargo.toml Outdated
Comment on lines 13 to 14
tokio = ["rtnetlink/tokio_socket"]
smol_socket = ["rtnetlink/smol_socket"]

Choose a reason for hiding this comment

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

I'd prefer if we just name them after the executor and drop the _socket.

src/lib.rs Outdated Show resolved Hide resolved
CHANGELOG.md Outdated
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.1.0] - [unreleased]

Choose a reason for hiding this comment

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

Missing version bump in Cargo.toml and it is also breaking atm because you are removing the IfWatcher symbol for linux :)

Copy link
Owner

@mxinden mxinden left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this @jxs!

src/linux.rs Show resolved Hide resolved
@mxinden
Copy link
Owner

mxinden commented Oct 14, 2022

@jxs if I am not mistaken this is not blocked on anything, right? Not to rush, just to make sure you are not waiting on anyone's input.

@jxs jxs force-pushed the feature-gate-runtime branch 2 times, most recently from 3a0c0ab to 7d6efc4 Compare October 17, 2022 19:47
Cargo.toml Outdated Show resolved Hide resolved
examples/if_watch.rs Show resolved Hide resolved
src/apple.rs Outdated
pub fn new() -> Result<Self> {
let (tx, rx) = mpsc::channel(1);
std::thread::spawn(|| background_task(tx));
T::spawn(async { background_task(tx) });

Choose a reason for hiding this comment

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

Spawning a non-async function is a bit odd. Can we make it async?

I think this will block the executor thread otherwise.

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 think this will block the executor thread otherwise.

interesting, why do you say this?

btw was able to test on a mac and confirm background_task's CFRunLoop::run_current() seems to run forever, and per tokio spawn_blocking doc for example:

This function is intended for non-async operations that eventually finish on their own. If you want to spawn an ordinary thread, you should use thread::spawn instead.

so we should probably maintain the plain old std::thread::spawn, wdyt?

Copy link

@thomaseizinger thomaseizinger Oct 18, 2022

Choose a reason for hiding this comment

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

I think this will block the executor thread otherwise.

interesting, why do you say this?

Unless interrupted otherwise, a task will run on an executor thread until it yields, i.e. returns Poll::Pending.

async_std has a mechanism to detect blocked executor threads and spawn new ones: https://async.rs/blog/stop-worrying-about-blocking-the-new-async-std-runtime/

Tokio doesn't though and it is a conscious design decision AFAIK.

btw was able to test on a mac and confirm background_task's CFRunLoop::run_current() seems to run forever, and per tokio spawn_blocking doc for example:

This function is intended for non-async operations that eventually finish on their own. If you want to spawn an ordinary thread, you should use thread::spawn instead.

But you are not using spawn_blocking right? So how is that relevant?

so we should probably maintain the plain old std::thread::spawn, wdyt?

Can we refactor this to be similar to the linux implementation where we register a waker and call it in a callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unless interrupted otherwise, a task will run on an executor thread until it yields, i.e. returns Poll::Pending.

async_std has a mechanism to detect blocked executor threads and spawn new ones: https://async.rs/blog/stop-worrying->about-blocking-the-new-async-std-runtime/

Tokio doesn't though and it is a conscious design decision AFAIK.

Right yeah, but a function just by not being async doesn't mean it will block the thread.

But you are not using spawn_blocking right? So how is that relevant?

because background_task's CFRunLoop::run_current() seems to run forever, therefore blocking the thread.

Can we refactor this to be similar to the linux implementation where we register a waker and call it in a callback?

Yeah I think so, but there still still need for a blocking thread as CFRunLoop::run_current() is blocking, while the Windows NotifyIpInterfaceChange isn't.

Choose a reason for hiding this comment

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

Unless interrupted otherwise, a task will run on an executor thread until it yields, i.e. returns Poll::Pending.
async_std has a mechanism to detect blocked executor threads and spawn new ones: https://async.rs/blog/stop-worrying->about-blocking-the-new-async-std-runtime/
Tokio doesn't though and it is a conscious design decision AFAIK.

Right yeah, but a function just by not being async doesn't mean it will block the thread.

That is correct. I think we are saying the same thing, it is CFRunLoop::run_current() that is the problem.

But you are not using spawn_blocking right? So how is that relevant?

because background_task's CFRunLoop::run_current() seems to run forever, therefore blocking the thread.

Can we refactor this to be similar to the linux implementation where we register a waker and call it in a callback?

Yeah I think so, but there still still need for a blocking thread as CFRunLoop::run_current() is blocking, while the Windows NotifyIpInterfaceChange isn't.

That really is unfortunate. I guess we will have to stick with a regular thread then for MacOS.

Copy link
Contributor Author

@jxs jxs Oct 22, 2022

Choose a reason for hiding this comment

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

That really is unfortunate. I guess we will have to stick with a regular thread then for MacOS.

yeah 😞 Reverted to an implementation similar to Windows ptal Thomas. Still, I think this approach makes sense and is more idiomatic the inicial one of overriding features

to fix android ci build. It was failing with: `libif_watch.so` doesn't exist.
Copy link

@thomaseizinger thomaseizinger left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM!

Copy link
Owner

@mxinden mxinden left a comment

Choose a reason for hiding this comment

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

Thanks for the thorough work on this.

CHANGELOG.md Outdated Show resolved Hide resolved
@mxinden mxinden merged commit 31c4780 into mxinden:master Nov 4, 2022
@mxinden
Copy link
Owner

mxinden commented Nov 4, 2022

Tagged and published.

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

Successfully merging this pull request may close these issues.

5 participants