Files
tarpc/tarpc/src/macros.rs

438 lines
13 KiB
Rust

// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[doc(hidden)]
#[macro_export]
macro_rules! as_item { ($i:item) => {$i} }
// Required because if-let can't be used with irrefutable patterns, so it needs
// to be special cased.
#[doc(hidden)]
#[macro_export]
macro_rules! client_methods {
(
{ $(#[$attr:meta])* }
$fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty
) => (
$(#[$attr])*
pub fn $fn_name(&self, $($arg: $in_),*) -> $crate::Result<$out> {
let reply = try!((self.0).rpc(request_variant!($fn_name $($arg),*)));
let __Reply::$fn_name(reply) = reply;
Ok(reply)
}
);
($(
{ $(#[$attr:meta])* }
$fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty
)*) => ( $(
$(#[$attr])*
pub fn $fn_name(&self, $($arg: $in_),*) -> $crate::Result<$out> {
let reply = try!((self.0).rpc(request_variant!($fn_name $($arg),*)));
if let __Reply::$fn_name(reply) = reply {
Ok(reply)
} else {
panic!("Incorrect reply variant returned from protocol::Clientrpc; expected `{}`, but got {:?}", stringify!($fn_name), reply);
}
}
)*);
}
// Required because if-let can't be used with irrefutable patterns, so it needs
// to be special cased.
#[doc(hidden)]
#[macro_export]
macro_rules! async_client_methods {
(
{ $(#[$attr:meta])* }
$fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty
) => (
$(#[$attr])*
pub fn $fn_name(&self, $($arg: $in_),*) -> Future<$out> {
fn mapper(reply: __Reply) -> $out {
let __Reply::$fn_name(reply) = reply;
reply
}
let reply = (self.0).rpc_async(request_variant!($fn_name $($arg),*));
Future {
future: reply,
mapper: mapper,
}
}
);
($(
{ $(#[$attr:meta])* }
$fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty
)*) => ( $(
$(#[$attr])*
pub fn $fn_name(&self, $($arg: $in_),*) -> Future<$out> {
fn mapper(reply: __Reply) -> $out {
if let __Reply::$fn_name(reply) = reply {
reply
} else {
panic!("Incorrect reply variant returned from protocol::Clientrpc; expected `{}`, but got {:?}", stringify!($fn_name), reply);
}
}
let reply = (self.0).rpc_async(request_variant!($fn_name $($arg),*));
Future {
future: reply,
mapper: mapper,
}
}
)*);
}
// Required because enum variants with no fields can't be suffixed by parens
#[doc(hidden)]
#[macro_export]
macro_rules! define_request {
($(@($($finished:tt)*))* --) => (as_item!(
#[allow(non_camel_case_types)]
#[derive(Debug, Serialize, Deserialize)]
enum __Request { $($($finished)*),* }
););
($(@$finished:tt)* -- $name:ident() $($req:tt)*) =>
(define_request!($(@$finished)* @($name) -- $($req)*););
($(@$finished:tt)* -- $name:ident $args: tt $($req:tt)*) =>
(define_request!($(@$finished)* @($name $args) -- $($req)*););
($($started:tt)*) => (define_request!(-- $($started)*););
}
// Required because enum variants with no fields can't be suffixed by parens
#[doc(hidden)]
#[macro_export]
macro_rules! request_variant {
($x:ident) => (__Request::$x);
($x:ident $($y:ident),+) => (__Request::$x($($y),+));
}
/// The main macro that creates RPC services.
///
/// Rpc methods are specified, mirroring trait syntax:
///
/// ```
/// # #![feature(custom_derive, plugin)]
/// # #![plugin(serde_macros)]
/// # #[macro_use] extern crate tarpc;
/// # extern crate serde;
/// # fn main() {}
/// # service! {
/// #[doc="Say hello"]
/// rpc hello(name: String) -> String;
/// # }
/// ```
///
/// Attributes can be attached to each rpc. These attributes
/// will then be attached to the generated `Service` trait's
/// corresponding method, as well as to the `Client` stub's rpcs methods.
///
/// The following items are expanded in the enclosing module:
///
/// * `Service` -- the trait defining the rpc service
/// * `Client` -- a client that makes synchronous requests to the rpc server
/// * `AsyncClient` -- a client that makes asynchronous requests to the rpc server
/// * `Future` -- a generic type returned by `AsyncClient`'s rpc's
/// * `serve` -- the function that starts the rpc server
///
/// **Warning**: In addition to the above items, there are a few expanded items that
/// are considered implementation details. As with the above items, shadowing
/// these item names in the enclosing module is likely to break things in confusing
/// ways:
///
/// * `__Server` -- an implementation detail
/// * `__Request` -- an implementation detail
/// * `__Reply` -- an implementation detail
#[macro_export]
macro_rules! service {
(
$(
$(#[$attr:meta])*
rpc $fn_name:ident( $( $arg:ident : $in_:ty ),* ) -> $out:ty;
)*
) => {
#[doc="The provided RPC service."]
pub trait Service: Send + Sync {
$(
$(#[$attr])*
fn $fn_name(&self, $($arg:$in_),*) -> $out;
)*
}
impl<P, S> Service for P
where P: Send + Sync + ::std::ops::Deref<Target=S>,
S: Service
{
$(
$(#[$attr])*
fn $fn_name(&self, $($arg:$in_),*) -> $out {
Service::$fn_name(&**self, $($arg),*)
}
)*
}
define_request!($($fn_name($($in_),*))*);
#[allow(non_camel_case_types)]
#[derive(Debug, Serialize, Deserialize)]
enum __Reply {
$(
$fn_name($out),
)*
}
/// An asynchronous RPC call
pub struct Future<T> {
future: $crate::protocol::Future<__Reply>,
mapper: fn(__Reply) -> T,
}
impl<T> Future<T> {
/// Block until the result of the RPC call is available
pub fn get(self) -> $crate::Result<T> {
self.future.get().map(self.mapper)
}
}
#[doc="The client stub that makes RPC calls to the server."]
pub struct Client($crate::protocol::Client<__Request, __Reply>);
impl Client {
#[doc="Create a new client that connects to the given address."]
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<Self>
where A: ::std::net::ToSocketAddrs,
{
let inner = try!($crate::protocol::Client::new(addr, timeout));
Ok(Client(inner))
}
client_methods!(
$(
{ $(#[$attr])* }
$fn_name($($arg: $in_),*) -> $out
)*
);
}
#[doc="The client stub that makes asynchronous RPC calls to the server."]
pub struct AsyncClient($crate::protocol::Client<__Request, __Reply>);
impl AsyncClient {
#[doc="Create a new asynchronous client that connects to the given address."]
pub fn new<A>(addr: A, timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<Self>
where A: ::std::net::ToSocketAddrs,
{
let inner = try!($crate::protocol::Client::new(addr, timeout));
Ok(AsyncClient(inner))
}
async_client_methods!(
$(
{ $(#[$attr])* }
$fn_name($($arg: $in_),*) -> $out
)*
);
}
struct __Server<S: 'static + Service>(S);
impl<S> $crate::protocol::Serve for __Server<S>
where S: 'static + Service
{
type Request = __Request;
type Reply = __Reply;
fn serve(&self, request: __Request) -> __Reply {
match request {
$(
request_variant!($fn_name $($arg),*) =>
__Reply::$fn_name((self.0).$fn_name($($arg),*)),
)*
}
}
}
#[doc="Start a running service."]
pub fn serve<A, S>(addr: A,
service: S,
read_timeout: ::std::option::Option<::std::time::Duration>)
-> $crate::Result<$crate::protocol::ServeHandle>
where A: ::std::net::ToSocketAddrs,
S: 'static + Service
{
let server = ::std::sync::Arc::new(__Server(service));
Ok(try!($crate::protocol::serve_async(addr, server, read_timeout)))
}
}
}
#[cfg(test)]
#[allow(dead_code)]
mod test {
extern crate env_logger;
use ServeHandle;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use test::Bencher;
fn test_timeout() -> Option<Duration> {
Some(Duration::from_secs(5))
}
#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct Foo {
pub message: String
}
mod my_server {
use super::Foo;
service! {
rpc hello(foo: Foo) -> Foo;
rpc add(x: i32, y: i32) -> i32;
}
}
struct Server;
impl my_server::Service for Server {
fn hello(&self, s: Foo) -> Foo {
Foo { message: format!("Hello, {}", &s.message) }
}
fn add(&self, x: i32, y: i32) -> i32 {
x + y
}
}
#[test]
fn serve_arc_server() {
my_server::serve("localhost:0", ::std::sync::Arc::new(Server), None)
.unwrap()
.shutdown();
}
#[test]
fn simple() {
let handle = my_server::serve( "localhost:0", Server, test_timeout()).unwrap();
let client = my_server::Client::new(handle.local_addr(), None).unwrap();
assert_eq!(3, client.add(1, 2).unwrap());
let foo = Foo { message: "Adam".into() };
let want = Foo { message: format!("Hello, {}", &foo.message) };
assert_eq!(want, client.hello(Foo { message: "Adam".into() }).unwrap());
drop(client);
handle.shutdown();
}
#[test]
fn simple_async() {
let handle = my_server::serve("localhost:0", Server, test_timeout()).unwrap();
let client = my_server::AsyncClient::new(handle.local_addr(), None).unwrap();
assert_eq!(3, client.add(1, 2).get().unwrap());
let foo = Foo { message: "Adam".into() };
let want = Foo { message: format!("Hello, {}", &foo.message) };
assert_eq!(want, client.hello(Foo { message: "Adam".into() }).get().unwrap());
drop(client);
handle.shutdown();
}
/// Tests a service definition with a fn that takes no args
mod qux {
service! {
rpc hello() -> String;
}
}
/// Tests a service definition with an import
mod foo {
use std::collections::HashMap;
service! {
#[doc="Hello bob"]
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
}
/// Tests a service definition with an attribute but no doc comment
#[deny(missing_docs)]
mod bar {
use std::collections::HashMap;
service! {
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
}
/// Tests a service definition with an attribute and a doc comment
#[deny(missing_docs)]
#[allow(unused)]
mod baz {
use std::collections::HashMap;
#[derive(Debug)]
pub struct Debuggable;
service! {
#[doc="Hello bob"]
#[inline(always)]
rpc baz(s: String) -> HashMap<String, String>;
}
}
#[test]
fn debug() {
println!("{:?}", baz::Debuggable);
}
mod hi {
service! {
rpc hello(s: String) -> String;
}
}
struct HelloServer;
impl hi::Service for HelloServer {
fn hello(&self, s: String) -> String {
format!("Hello, {}!", s)
}
}
// Prevents resource exhaustion when benching
lazy_static! {
static ref HANDLE: Arc<Mutex<ServeHandle>> = {
let handle = hi::serve("localhost:0", HelloServer, None).unwrap();
Arc::new(Mutex::new(handle))
};
static ref CLIENT: Arc<Mutex<hi::AsyncClient>> = {
let addr = HANDLE.lock().unwrap().local_addr().clone();
let client = hi::AsyncClient::new(addr, None).unwrap();
Arc::new(Mutex::new(client))
};
}
#[bench]
fn hello(bencher: &mut Bencher) {
let _ = env_logger::init();
let client = CLIENT.lock().unwrap();
let concurrency = 100;
let mut futures = Vec::with_capacity(concurrency);
let mut count = 0;
bencher.iter(|| {
futures.push(client.hello("Bob".into()));
count += 1;
if count % concurrency == 0 {
for f in futures.drain(..) {
f.get().unwrap();
}
}
});
}
}