changed 2 years ago
Linked with GitHub

AFIT Case Study: AWS SDK

https://docs.rs/aws-credential-types/0.54.1/aws_credential_types/provider/trait.ProvideCredentials.html

https://docs.rs/aws-smithy-async/0.54.3/aws_smithy_async/rt/sleep/trait.AsyncSleep.html

https://docs.rs/aws-config/0.54.1/aws_config/meta/region/trait.ProvideRegion.html

Question this doc answers: what features do we need to meet the needs of AWS SDK from async traits? What works well, what can we workaround, and where are the gaps relative to the current state on nightly.

TL;DR

  • Conclusion:
    • given send bounds, the current support for async traits would be sufficient for ergonomic use in the AWS SDK*
  • Workarounds:
    • erased dyn trait, but it's hidden from clients

Where would AWS SDK like to use async traits, and how do they solve it today?

Cover ProvideCredentials

List other examples and say they are similar, and so we ignore them.

How would this work with async traits as proposed?

From a client's perspective

impl SomeTrait for MyType {
    async fn();
}

builder
    .with_new(MyType);

How it's implemented

  • send bounds, need for that
  • dyn dispatch, erasure

Dyn dispatch as first-class feature

  • no real complications here

Variant: with async syntax

Provide Credentials

Trait for creating custom credential providers; anything that gives AWS credentials, could be from disk, but also from servers. These are async because they may make HTTP requests and so forth under the hood.

Today

In the library:

pub trait ProvideCredentials: Send + Sync + Debug {
    fn provide_credentials<'a>(&'a self) -> ProvideCredentials<'a>
    where
        Self: 'a;
}

pub struct ProvideCredentials<'a>(..);

impl<'a> ProvideCredentials<'a> {
    pub fn new(future: impl Future<Output = Result> + Send + 'a) -> Self;
}

// https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.credentials_provider
impl ConfigLoader {
    pub fn credentials_provider(
        self,
        credentials_provider: impl ProvideCredentials + 'static
    ) -> Self;
}

In user's code:

impl ProvideCredentials for MyProvider {
    fn provide_credentials<'a>(&'a self) -> ProvideCredentials<'a>
    where
        Self: 'a
    {
        ProvideCredentials::new(async move {
            ...
        })
    }
}

related PR: https://github.com/awslabs/smithy-rs/pull/1359

then later

let config_loader: SdkConfig = ConfigLoader::new()
    .credentials_provider(MyProvider)
    .load()
    .await;
  • Credentials provider is embedded in the SdkConfig
    • in case credentials expire
  • Needs to be sendable across threads for multithreaded users

Future

pub type Result = std::result::Result<Credentials, error::CredentialsError>;

pub trait ProvideCredentials: Send + Sync + Debug {
    async fn provide_credentials<'a>(&'a self) -> Result
    where
        Self: 'a;
}

// https://docs.rs/aws-config/0.54.1/aws_config/struct.ConfigLoader.html#method.credentials_provider
impl ConfigLoader {
    pub fn credentials_provider(
        self,
        credentials_provider: impl ProvideCredentials + 'static
    ) -> Self {
        let b: Box<dyn ProvideCredentialsDyn> = Box::new(credentials_provider);
        self.field = b;
        self
    }
}

// Private to your library

trait ProvideCredentialsDyn {
    fn provide_credentials<'a>(&'a self) -> Box<dyn Future<Output = Result> + Send>
    where
        Self: 'a;
}

impl<T> ProvideCredentialsDyn for T
where
    T: ProvideCredentials<credentials_provider(..): Send>,
    //                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // needed because we have to say that the future is Send so we can create the future
{
    fn provide_credentials<'a>(&'a self) -> Box<dyn Future<Output = Result> + Send>
    where
        Self: 'a
    {
        Box::new(ProvideCredentials::provide_credentials(self))
    }
}

https://docs.rs/aws-smithy-async/0.54.3/aws_smithy_async/rt/sleep/trait.AsyncSleep.html

pub trait AsyncSleep: Debug + Send + Sync {
    // Required method
    fn sleep(&self, duration: Duration) -> Sleep ⓘ;
}

https://docs.rs/aws-config/0.54.1/aws_config/meta/region/trait.ProvideRegion.html

is similar

https://docs.rs/aws-smithy-http/0.33.1/aws_smithy_http/body/struct.SdkBody.html

https://docs.rs/http-body/0.4.5/http_body/trait.Body.html

https://docs.rs/aws-smithy-http/latest/src/aws_smithy_http/byte_stream.rs.html#441

want private impls

currently workaround with this Inner type so that other people can't rely on having implemented thin

using stream in public API for event streams

https://docs.rs/aws-smithy-http/latest/src/aws_smithy_http/byte_stream.rs.html#541

impl<B> AsyncIterator for Inner<B>
where
    B: http_body::Body<Data = Bytes>,
{
    type Item = Result<Bytes, B::Error>;

    async fn next(&mut self, {
        self.project().body.poll_data(cx)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let size_hint = http_body::Body::size_hint(&self.body);
        let lower = size_hint.lower().try_into();
        let upper = size_hint.upper().map(|u| u.try_into()).transpose();

        match (lower, upper) {
            (Ok(lower), Ok(upper)) => (lower, upper),
            (Err(_), _) | (_, Err(_)) => {
                panic!("{}", SIZE_HINT_32_BIT_PANIC_MESSAGE)
            }
        }
    }
}

https://docs.rs/aws-smithy-http/latest/aws_smithy_http/event_stream/struct.EventStreamSender.html#impl-From<S>-for-EventStreamSender<T,+E>

https://docs.rs/aws-sdk-s3/0.24.0/aws_sdk_s3/paginator/struct.ListObjectsV2Paginator.html#method.send

ff

async trait ProvideCredentials {
    async fn ...
}

other relevant features

Select a repo