Skip to content

[illumos-utils] Use tokio::process::Command, not std::process::Command #8141

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

Merged
merged 8 commits into from
May 16, 2025

Conversation

smklein
Copy link
Collaborator

@smklein smklein commented May 12, 2025

Many commands in illumos-utils bottomed out in a call to "std::process::Command", eventually
exec-ing a process. These commands were often used from asynchronous code, even though they
could block the calling thread.

This PR converts many - but not all - of these calls to use tokio::process::Command instead, which
no longer blocks the calling thread.

@smklein smklein changed the title Async illumos utils [illumos-utils] Use tokio::process::Commands instead of std::process::Command May 12, 2025
@smklein smklein changed the title [illumos-utils] Use tokio::process::Commands instead of std::process::Command [illumos-utils] Use tokio::process::Command, not std::process::Command May 12, 2025
"error" => ?e,
);
let DiagnosticsSnapshot { log, snapshot, .. } = self;
tokio::task::spawn({
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To confirm: is it critical that the snapshot blocks the drop method? Async drop does not exist in rust.

This API, as implemented, creates the background task but does not await for it to complete.

@@ -6,12 +6,13 @@

use crate::link::{Link, LinkKind};
use crate::zone::IPADM;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The changes in this file - and others within illumos-utils/src/... - are representative of "what actually is changing in this PR":

  • std::process::Command -> tokio::process::Command
  • This makes the function async
  • The rest of the changes are fall-out from this, identifying blocks of code as asynchronous

@smklein smklein marked this pull request as ready for review May 13, 2025 16:52
Comment on lines 980 to 985
// Free all of the log_snapshots
drop(log_snapshots);

let snapshots = get_sled_diagnostics_snapshots(zfs_filesystem);
let snapshots =
get_sled_diagnostics_snapshots(zfs_filesystem).await;
assert!(snapshots.is_empty(), "no snapshots left behind");
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's critical that the drop method is synchronous however it does introduce a race condition in the test here, we may be able to work around it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've changed the behavior here to use a more explicit drop, and log errors if this wasn't handled. I also tried to refactor above to make this case less likely -- cleaning up snapshots at a higher-level in the call to get_zone_logs

@@ -175,6 +176,7 @@ pub struct Dladm(());
/// Describes the API for interfacing with Data links.
///
/// This is a trait so that it can be faked out for tests.
#[async_trait::async_trait]
Copy link
Contributor

@jgallagher jgallagher May 14, 2025

Choose a reason for hiding this comment

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

This is fine, but I'm curious (and maybe this is a "Friday afternoon Rust chat" question more than a PR question) - is there a reason to prefer async_trait over returning impl Future<Output = ...> + Send now that Rust supports that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this is slightly more painful for default methods - I see elsewhere we have the pattern of:

trait Api {
  fn foobar(&self) -> impl Future<Output=Baz> + Send;
}

impl Api for MyStruct {
  async fn foobar(&self) -> Baz { ... }
}

But if I try to use a default impl of:

trait Api {
  fn foobar(&self) -> impl Future<Output=Baz> + Send {
     // Do anything async
  }
}

I'll get the error the ".await" is only allowed in async blocks, and foobar isn't an async block.

Copy link
Contributor

Choose a reason for hiding this comment

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

Might be able to get away with

trait Api {
    fn foobar(&self) -> impl Future<Output=Baz> + Send {
        async {
            // Do anything async
        }
    }
}

but that might also be kind of a pain if you're doing anything with self. Anyway - it's a good point; async_trait does seem to be smoother still, at least for this case.

zone: Option<&'a str>,
addrobj: &AddrObject,
addrtype: AddressRequest,
) -> Result<IpNetwork, EnsureAddressError> {
|zone, addrobj, addrtype| -> Result<IpNetwork, anyhow::Error> {
match Self::get_address_impl(zone, addrobj) {
#[allow(clippy::redundant_closure_call)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this redundant closure? (If so maybe worth a short comment explaining why)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll avoid it by pulling this out to a new method instead. We're using it to make translation of the error type more convenient.


async fn destroy(&mut self) -> Result<(), LogError> {
if !self.destroyed {
self.destroyed = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be set after Zfs::destroy_snapshot() returns Ok(_)? (As written the caller couldn't retry destruction, although maybe that doesn't matter at the moment.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, I'll move it down. I don't think we're trying to call this again from any pub codepaths, but I don't see a problem with making it retryable either.

@smklein smklein merged commit 19c56cf into main May 16, 2025
16 checks passed
@smklein smklein deleted the async-illumos-utils branch May 16, 2025 19:14
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.

4 participants