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

WIP: ?Send support #1122

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Conversation

omarabid
Copy link

This PR removes support for Send and Sync. The use case is for people running async-graphql on a single threaded environment and who are limited by having types that are not Send and Sync. The current solution to wrap these types is very limited and messy.

This PR is intrusive; however, if you are open to accepting this as a feature (ie: no-send), I'd work on making the necessary changes. We are adopting async-graphql as our main graphql server; so I'll be updating from upstream and merging conflicts accordingly for the time being.

@sunli829
Copy link
Collaborator

I'm looking forward to this PR, come on bro! 👏

@omarabid
Copy link
Author

@sunli829 Thanks for being to this feature. I'll be working on it for the next couple weeks!

@ClementNerma
Copy link

Very interesting! May I ask in which case do you have a single-threaded environment for a GraphQL API which is, by nature, more tailored to be split across multiple threads?

@maraisr
Copy link
Contributor

maraisr commented Oct 31, 2022

@ClementNerma I for one have a use-case in a WASM based runtime, like running async-graphql in a Cloudflare Worker, which is single threaded.

@sunli829 sunli829 force-pushed the master branch 3 times, most recently from 7eabbe3 to 912feb3 Compare November 11, 2022 15:53
@maraisr
Copy link
Contributor

maraisr commented Nov 28, 2022

@omarabid whats left to ship this?

@sunli829
Copy link
Collaborator

https://crates.io/crates/send_wrapper

You can use this, there's a slight performance penalty.

@omarabid
Copy link
Author

@omarabid whats left to ship this?

The PR is working (though the latest from master is not merged) and I am using it. I'm caught travelling but hopefully will get to it this week.

Here are the steps if you are looking to help:

  1. Merge the latest from master
  2. Add the feature tag around the lines changed. I still haven't looked at the best way of doing this. Maybe you can chime in with a suggestion here.

@omarabid
Copy link
Author

Hi @sunli829,

Please advice on the following implementation

/// Used to specify the GraphQL Type name.
#[cfg(feature = "no-send")]
pub trait TypeName {
    /// Returns a GraphQL type name.
    fn type_name() -> Cow<'static, str>;
}

#[cfg(not(feature = "no-send"))]
pub trait TypeName: Send + Sync {
    /// Returns a GraphQL type name.
    fn type_name() -> Cow<'static, str>;
}

Alternatively, you can have a default send feature. The drawback here, of course, is that you need to keep track of two copies of TypeName. What do you think? And do you have have a better suggestion to approach this?

@sunli829
Copy link
Collaborator

I think this can be better, what do you think?

#[cfg(feature = "no-send")]
pub trait ThreadedModel {
    // wrap the future in a box
    fn boxed_future<F: Future>(fut: F) {
        Box::new(fut)
    }
}

#[cfg(not(feature = "no-send"))]
pub trait ThreadedModel: Send + Sync {
    fn boxed_future<F: Future + Send + Sync>(fut: F) {
        Box::new(fut)
    }
}

pub trait TypeName: ThreadedModel  {
    /// Returns a GraphQL type name.
    fn type_name() -> Cow<'static, str>;
}

@omarabid
Copy link
Author

omarabid commented Dec 12, 2022

@sunli829 This has the interesting effect of requiring that any type that implements TypeName must also implement ThreadedModel but that might also make sense?

Also, is this needed?

    fn boxed_future<F: Future + Send + Sync>(fut: F) {
        Box::new(fut)
    }

@sunli829
Copy link
Collaborator

This has the interesting effect of requiring that any type that implements TypeName must also implement ThreadedModel but that might also make sense?

I think we can use macros to implement ThreadedModel for all types.


A different BoxFuture definition needs to be used here.

#[cfg(feature = "no-send")]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T>> + 'a>>

#[cfg(not(feature = "no-send"))]
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T>> + 'a + Send>>

type BoxFieldFuture<'a> = BoxFuture<'a, ServerResult<(Name, Value)>>;

type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = ServerResult<(Name, Value)>> + 'a + Send>>;

@omarabid omarabid mentioned this pull request Apr 15, 2023
@omarabid
Copy link
Author

Latest from upstream merged.

@sunli829 Is this feature still of interest? I am looking to give it another shot and have a bit of free time in the next couple weeks. Your guidance in that will be very helpful.

@sunli829
Copy link
Collaborator

Yes, I hope to have it done before the 6.0 release.

@omarabid
Copy link
Author

omarabid commented Jun 19, 2023

@sunli829
Please advice on the following (ps: This is my first attempt with macros):

proc_macro implementation

#[proc_macro_attribute]
pub fn threaded_model(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut trait_item = parse_macro_input!(item as ItemTrait);

    if !cfg!(feature = "no-send") {
        let send_bound: TypeParamBound = syn::parse_quote!(Send);
        let sync_bound: TypeParamBound = syn::parse_quote!(Sync);

        trait_item.supertraits.push(syn::parse_quote!(#send_bound));
        trait_item.supertraits.push(syn::parse_quote!(#sync_bound));
    }

    TokenStream::from(quote! { #trait_item })
}

Usage

#[threaded_model]
pub trait TypeName  {
    /// Returns a GraphQL type name.
    fn type_name() -> Cow<'static, str>;
}

There is one catch, however. The no-send feature doesn't seem like it transcends to the derive crate. Any ideas how to do that?

@sunli829
Copy link
Collaborator

Maybe the following way will be easier, what do you think?

#[cfg(not(feature="no-send"))]
trait ThreadedModel: Send + Sync {}

#[cfg(feature="no-send")]
trait ThreadedModel {}

pub trait TypeName: ThreadedModel {
    /// Returns a GraphQL type name.
    fn type_name() -> Cow<'static, str>;
}

@omarabid
Copy link
Author

@sunli829 That's what I've been trying to do (and mostly because I am new to macros). The problem is that the Send and Sync traits are marker traits; and they behave differently from a normal trait. As a result, you don't have implied implementation and you have to implement ThreadedModel for every type that implements TypeName.

I've pushed that attempt here: https://github.com/omarabid/async-graphql/tree/trait if you have time to debug it.

@sunli829
Copy link
Collaborator

Looks great, do you need my help fixing the procedural macros? @omarabid

@omarabid
Copy link
Author

@omarabid I'm still new to macros/proc_macros. I'll give it a couple days and come back with an implementation for you to review.

@balaatsky
Copy link

@omarabid would you like some help?

@omarabid
Copy link
Author

@pandranki-skytv It has been a while since I tried my hands on this, so the details are kind of fuzzy in my brain. However, my latest attempt landed me here: https://users.rust-lang.org/t/optional-send-sync-implementations-through-a-feature-flag/96007 but I never got to H2CO3 proposition. I think it'll be a good idea to start from trying to use a simple Trait in order to understand the different problematic edge cases and then try H2CO3 proposition. I'm a bit swamped now but could get back to it in the next couple weeks.

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.

None yet

5 participants