Skip to content

Commit

Permalink
Function Worker (#267)
Browse files Browse the repository at this point in the history
* Fix typo.

* Move worker into actor folder.

* Add some oneshot implementation.

* Finish oneshot worker.

* Strip 'static from worker definition.

* Strip serde from message definition.

* Update example.

* Initial Implementation of Reactor Worker.

* Implement Spawner and Registrar.

* Rename Receiver to Source.

* Allow any stream.

* Split bridge into receiver and sender.

* Add docs.

* Add oneshot macro.

* Add reactor macro.

* Allows synchronous function for oneshot worker.

* Move to Sink.

* Adjust design of reactor worker.

* Update reactor macro.

* Adjust oneshot signature.

* Fix Actor Worker.

* Make a reactor example.

* Switch to send.

* Minor adjustments.

* Add some tests.

* Update implementation.

* Fix CI.

* Skip macros in browser tests.

* Adjust implementation.

* (some) documentation

* add tests for prime example

* cargo fmt

* update CI

* fix macro tests

* syn 2

* fix typo

* Update crates/worker-macros/src/worker_fn.rs

Co-authored-by: Kaede Hoshikawa <futursolo@users.noreply.github.com>

* temp: disable chrome in workers CI

* disable prime example in CI

the example crashes only in CI for some reason

---------

Co-authored-by: Muhammad Hamza <muhammadhamza1311@gmail.com>
  • Loading branch information
futursolo and ranile authored Jun 28, 2023
1 parent aa0dbcd commit ade9965
Show file tree
Hide file tree
Showing 60 changed files with 2,102 additions and 253 deletions.
16 changes: 12 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ jobs:
if [[ "$x" == "net" ]]; then
continue
fi
if [[ "$x" == "worker-macros" ]]; then
continue
fi
wasm-pack test --headless --firefox --chrome crates/$x --all-features
wasm-pack test --headless --firefox --chrome crates/$x --no-default-features
done
Expand Down Expand Up @@ -107,6 +110,11 @@ jobs:
test-worker:
name: Test gloo-worker
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# example: [ markdown, prime ]
example: [ markdown ]
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
Expand Down Expand Up @@ -135,16 +143,16 @@ jobs:
- name: Build and Run Test Server
run: |
cargo build -p example-markdown --bin example_markdown_test_server
nohup target/debug/example_markdown_test_server examples/markdown/dist &
cargo build -p example-${{ matrix.example }} --bin example_${{ matrix.example }}_test_server
nohup target/debug/example_${{ matrix.example }}_test_server examples/${{ matrix.example }}/dist &
- name: Build Test Worker
run: |
trunk build examples/markdown/index.html
trunk build examples/${{ matrix.example }}/index.html
- name: Run tests for gloo worker
run: |
wasm-pack test --headless --firefox --chrome examples/markdown
wasm-pack test --headless --chrome --firefox examples/${{ matrix.example }}
test-net:
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ members = [
"crates/utils",
"crates/history",
"crates/worker",
"crates/worker-macros",
"crates/net",

"examples/markdown",
"examples/clock",
"examples/prime",
]

# Passing arguments to the docsrs builder in order to properly document cfg's.
Expand Down
24 changes: 24 additions & 0 deletions crates/worker-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "gloo-worker-macros"
version = "0.1.0"
authors = ["Rust and WebAssembly Working Group"]
edition = "2021"
readme = "README.md"
description = "Convenience crate for working with Web Workers"
repository = "https://github.com/rustwasm/gloo/tree/master/crates/worker"
homepage = "https://github.com/rustwasm/gloo"
license = "MIT OR Apache-2.0"
categories = ["api-bindings", "asynchronous", "wasm"]

[lib]
proc-macro = true

[dependencies]
proc-macro-crate = "1.2.1"
proc-macro2 = "1.0.47"
quote = "1.0.21"
syn = { version = "2.0.15", features = ["full"] }

[dev-dependencies]
trybuild = "1"
gloo = { path = "../..", features = ["futures"] }
24 changes: 24 additions & 0 deletions crates/worker-macros/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div align="center">

<h1><code>gloo-worker</code></h1>

<p>
<a href="https://crates.io/crates/gloo-worker"><img src="https://img.shields.io/crates/v/gloo-worker.svg?style=flat-square" alt="Crates.io version" /></a>
<a href="https://crates.io/crates/gloo-worker"><img src="https://img.shields.io/crates/d/gloo-worker.svg?style=flat-square" alt="Download" /></a>
<a href="https://docs.rs/gloo-worker"><img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square" alt="docs.rs docs" /></a>
</p>

<h3>
<a href="https://docs.rs/gloo-worker">API Docs</a>
<span> | </span>
<a href="https://github.com/rustwasm/gloo/blob/master/CONTRIBUTING.md">Contributing</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>

<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>

Gloo workers are a way to offload tasks to web workers. These are run concurrently using
[web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers).
It provides a neat abstraction over the browser's Web Workers API which can be consumed from anywhere.
30 changes: 30 additions & 0 deletions crates/worker-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use proc_macro::TokenStream;
use syn::parse_macro_input;

mod oneshot;
mod reactor;
mod worker_fn;

use oneshot::{oneshot_impl, OneshotFn};
use reactor::{reactor_impl, ReactorFn};
use worker_fn::{WorkerFn, WorkerName};

#[proc_macro_attribute]
pub fn reactor(attr: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as WorkerFn<ReactorFn>);
let attr = parse_macro_input!(attr as WorkerName);

reactor_impl(attr, item)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

#[proc_macro_attribute]
pub fn oneshot(attr: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as WorkerFn<OneshotFn>);
let attr = parse_macro_input!(attr as WorkerName);

oneshot_impl(attr, item)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
133 changes: 133 additions & 0 deletions crates/worker-macros/src/oneshot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{parse_quote, Ident, ReturnType, Signature, Type};

use crate::worker_fn::{WorkerFn, WorkerFnType, WorkerName};

pub struct OneshotFn {}

impl WorkerFnType for OneshotFn {
type OutputType = Type;
type RecvType = Type;

fn attr_name() -> &'static str {
"oneshot"
}

fn worker_type_name() -> &'static str {
"oneshot"
}

fn parse_recv_type(sig: &Signature) -> syn::Result<Self::RecvType> {
let mut inputs = sig.inputs.iter();
let arg = inputs
.next()
.ok_or_else(|| syn::Error::new_spanned(&sig.ident, "expected 1 argument"))?;

let ty = Self::extract_fn_arg_type(arg)?;

Self::assert_no_left_argument(inputs, 1)?;

Ok(ty)
}

fn parse_output_type(sig: &Signature) -> syn::Result<Self::OutputType> {
let ty = match &sig.output {
ReturnType::Default => {
parse_quote! { () }
}
ReturnType::Type(_, ty) => *ty.clone(),
};

Ok(ty)
}
}

pub fn oneshot_impl(
name: WorkerName,
mut worker_fn: WorkerFn<OneshotFn>,
) -> syn::Result<TokenStream> {
worker_fn.merge_worker_name(name)?;

let struct_attrs = worker_fn.filter_attrs_for_worker_struct();
let oneshot_impl_attrs = worker_fn.filter_attrs_for_worker_impl();
let phantom_generics = worker_fn.phantom_generics();
let oneshot_name = worker_fn.worker_name();
let fn_name = worker_fn.inner_fn_ident();
let inner_fn = worker_fn.print_inner_fn();

let WorkerFn {
recv_type: input_type,
generics,
output_type,
vis,
is_async,
..
} = worker_fn;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let fn_generics = ty_generics.as_turbofish();

let in_ident = Ident::new("_input", Span::mixed_site());

let fn_call = if is_async {
quote! { #fn_name #fn_generics (#in_ident).await }
} else {
quote! { #fn_name #fn_generics (#in_ident) }
};
let crate_name = WorkerFn::<OneshotFn>::worker_crate_name();

let quoted = quote! {
#(#struct_attrs)*
#[allow(unused_parens)]
#vis struct #oneshot_name #generics #where_clause {
inner: ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = #output_type>>>,
_marker: ::std::marker::PhantomData<(#phantom_generics)>,
}

// we cannot disable any lints here because it will be applied to the function body
// as well.
#(#oneshot_impl_attrs)*
impl #impl_generics ::#crate_name::oneshot::Oneshot for #oneshot_name #ty_generics #where_clause {
type Input = #input_type;

fn create(#in_ident: Self::Input) -> Self {
#inner_fn

Self {
inner: ::std::boxed::Box::pin(
async move {
#fn_call
}
),
_marker: ::std::marker::PhantomData,
}
}
}

impl #impl_generics ::std::future::Future for #oneshot_name #ty_generics #where_clause {
type Output = #output_type;

fn poll(mut self: ::std::pin::Pin<&mut Self>, cx: &mut ::std::task::Context<'_>) -> ::std::task::Poll<Self::Output> {
::std::future::Future::poll(::std::pin::Pin::new(&mut self.inner), cx)
}
}

impl #impl_generics ::#crate_name::Registrable for #oneshot_name #ty_generics #where_clause {
type Registrar = ::#crate_name::oneshot::OneshotRegistrar<Self>;

fn registrar() -> Self::Registrar {
::#crate_name::oneshot::OneshotRegistrar::<Self>::new()
}
}

impl #impl_generics ::#crate_name::Spawnable for #oneshot_name #ty_generics #where_clause {
type Spawner = ::#crate_name::oneshot::OneshotSpawner<Self>;

fn spawner() -> Self::Spawner {
::#crate_name::oneshot::OneshotSpawner::<Self>::new()
}
}
};

Ok(quoted)
}
Loading

0 comments on commit ade9965

Please sign in to comment.