Skip to content

[Discussion] A more ergonomic way to deal with 'blocking' futures #19

Description

@Byron

At gitoxide, a git implementation in Rust, most code is blocking as it relies heavily on operations which aren't available in 'real' async or are CPU bound.

When implementing the server side or clients it becomes evident that due to the ecosystem one won't get around using async, and those using gitoxide for custom clients or on the server will have to integrate with async crates or framework.

To get started with this and make what's there more flexible the client side is now available in async and a prototype client program exists to test the blocking/async interoperation from start to end.

As all git repository interactions are blocking, async client code coming from the transport or protocol layers will have to call into that. The async parts must not block the executor, and parts of or the entire operation have to be unblocked.

With the current API this looks like this:

        let transport = net::connect(url.as_bytes(), protocol.unwrap_or_default().into()).await?;
        let mut delegate = CloneDelegate {
            ctx,
            directory,
            refs_directory,
            ref_filter: None,
        };
        blocking::unblock(move || {
            futures_lite::future::block_on(protocol::fetch(
                transport,
                delegate,
                protocol::credentials::helper,
                progress,
            ))
        })
        .await?;

Fully async portions remain async, but those who are at least partially blocking must be entirely unblocked. The need for futures_lite::future::block_on seems avoidable especially when looking at the underlying implementation which relies on Executor::spawn(future) receiving a future.

pub async fn unblock<T, F>(f: F) -> T
where
    F: FnOnce() -> T + Send + 'static,
    T: Send + 'static,
{
    Executor::spawn(async move { f() }).await
}

If there was a function that takes a future, the code above could be simplified to the following:

        blocking::spawn(protocol::fetch(
            transport,
            delegate,
            protocol::credentials::helper,
            progress,
        ))
        .await?;

I would love to hear your thoughts about this usecase, and if there is interest will be happy to contribute.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions