Skip to content

Commit

Permalink
Debug serialization macro (#55)
Browse files Browse the repository at this point in the history
### Summary

Adds a new (internal) macro that conditionally serializes and
deserializes messages in debug builds.

### Motivation

Resolves #10 

Adds a safety mechanism to ensure there is no hidden state sharing
between sender and receivers via messages.

### Test Plan

+ [x] libraries compile
+ [x] examples compile & run
  • Loading branch information
JohnMurray committed May 2, 2023
1 parent f751a59 commit e990e04
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 28 deletions.
4 changes: 4 additions & 0 deletions busan-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ pub fn message(input: TokenStream) -> TokenStream {
fn as_any(&self) -> &dyn ::std::any::Any {
self
}

fn encode_to_vec2(&self) -> Vec<u8> {
prost::Message::encode_to_vec(self)
}
}
};

Expand Down
8 changes: 4 additions & 4 deletions examples/ping_pong/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use busan::actor::{Actor, ActorInit, Context};
use busan::config::ActorSystemConfig;
use busan::message::common_types::{I32Wrapper, StringWrapper};
use busan::message::{Message, ToMessage};
use busan::message::Message;
use busan::system::ActorSystem;
use std::thread;

Expand Down Expand Up @@ -36,14 +36,14 @@ impl ActorInit for Pong {
impl Actor for Ping {
fn before_start(&mut self, mut ctx: Context) {
let pong_addr = Some(ctx.spawn_child::<_, Pong>("pong", &I32Wrapper::default()));
ctx.send_message(pong_addr.as_ref().unwrap(), "ping".to_message());
ctx.send_message(pong_addr.as_ref().unwrap(), "ping");
}

fn receive(&mut self, ctx: Context, msg: Box<dyn Message>) {
// Print the message and respond with a "ping"
if let Some(strMsg) = msg.as_any().downcast_ref::<StringWrapper>() {
println!("received message: {}", strMsg.value);
ctx.send_message(ctx.sender(), "ping".to_message());
ctx.send_message(ctx.sender(), "ping");
}
}
}
Expand All @@ -52,7 +52,7 @@ impl Actor for Pong {
// Print the message and respond with a "pong"
if let Some(strMsg) = msg.as_any().downcast_ref::<StringWrapper>() {
println!("received message: {}", strMsg.value);
ctx.send_message(ctx.sender(), "pong".to_message());
ctx.send_message(ctx.sender(), "pong");
}
}
}
Expand Down
23 changes: 20 additions & 3 deletions src/actor/actor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::actor::{ActorAddress, Letter, SenderType};
use crate::message::Message;
use crate::message::{Message, ToMessage};
use crate::system::RuntimeManagerRef;
use crossbeam_channel::Receiver;
use log::{trace, warn};
Expand Down Expand Up @@ -64,6 +64,17 @@ impl ActorCell {
}
}

macro_rules! debug_serialize_msg {
($msg:expr, $T:tt) => {
if cfg!(debug_assertions) {
let bytes = $msg.encode_to_vec2();
$T::decode(bytes.as_slice()).unwrap()
} else {
$msg
}
};
}

/// Actor context object used for performing actions that interact with the running
/// actor-system, such as spawning new actors.
pub struct Context<'a> {
Expand Down Expand Up @@ -93,7 +104,11 @@ impl Context<'_> {
address
}

pub fn send_message(&self, addr: &ActorAddress, message: Box<dyn Message>) {
pub fn send_message<M: Message + Default + 'static, T: ToMessage<M>>(
&self,
addr: &ActorAddress,
message: T,
) {
// Validate that the address is resolved (this is a blocking call to the runtime
// manager if unresolved).
if !addr.is_resolved() {
Expand All @@ -112,8 +127,10 @@ impl Context<'_> {
// forwarded to the dead letter queue.
debug_assert!(addr.is_resolved(), "Address {} is not resolved", addr);

let message = debug_serialize_msg!(message.to_message(), M);

// Send the message to the resolved address
addr.send(Some(self.address.clone()), message);
addr.send(Some(self.address.clone()), Box::new(message));
}

pub fn sender(&self) -> &'_ ActorAddress {
Expand Down
41 changes: 22 additions & 19 deletions src/message/common_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ macro_rules! impl_to_message_for_primitive {
impl_to_message_for_primitive!($t, $wrapper, |x| x);
};
($t:ty, $wrapper:ident, $converter:expr $(, $deref:tt)?) => {
impl ToMessage for $t {
fn to_message(self) -> Box<dyn Message> {
Box::new($wrapper {
impl ToMessage<$wrapper> for $t {
fn to_message(self) -> $wrapper {
$wrapper {
value: $converter($($deref)* self),
})
}
}
fn is_primitive<L: message::private::IsLocal>(&self) -> bool {
return true;
Expand Down Expand Up @@ -50,39 +50,39 @@ impl_to_message_for_primitive!(&str, StringWrapper, |x: &str| x.to_string());
macro_rules! impl_to_message_for_primitive_list {
// Owned types that don't need conversion
($t:ty, $wrapper:ident) => {
impl ToMessage for Vec<$t> {
fn to_message(self) -> Box<dyn Message> {
Box::new($wrapper { values: self })
impl ToMessage<$wrapper> for Vec<$t> {
fn to_message(self) -> $wrapper {
$wrapper { values: self }
}
}
};
// Owned types that need conversion
($t:ty, $wrapper:ident, $converter:expr) => {
impl ToMessage for Vec<$t> {
fn to_message(self) -> Box<dyn Message> {
Box::new($wrapper {
impl ToMessage<$wrapper> for Vec<$t> {
fn to_message(self) -> $wrapper {
$wrapper {
values: self.iter().map(|x| $converter(*x)).collect(),
})
}
}
}
};
// Borrowed types that don't need conversion
(&$t:ty, $wrapper:ident, clone) => {
impl ToMessage for Vec<$t> {
fn to_message(self) -> Box<dyn Message> {
Box::new($wrapper {
impl ToMessage<$wrapper> for Vec<$t> {
fn to_message(self) -> $wrapper {
$wrapper {
values: self.clone(),
})
}
}
}
};
// Borrowed types that need conversion
(&$t:ty, $wrapper:ident, $converter:expr) => {
impl ToMessage for Vec<$t> {
fn to_message(self) -> Box<dyn Message> {
Box::new($wrapper {
impl ToMessage<$wrapper> for Vec<$t> {
fn to_message(self) -> $wrapper {
$wrapper {
values: self.iter().map(|x| $converter(x)).collect(),
})
}
}
}
};
Expand All @@ -109,6 +109,9 @@ macro_rules! impl_busan_message {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn encode_to_vec2(&self) -> Vec<u8> {
prost::Message::encode_to_vec(self)
}
}
};
}
Expand Down
18 changes: 16 additions & 2 deletions src/message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,34 @@ pub mod common_types;
pub trait Message: prost::Message {
fn as_any(&self) -> &dyn std::any::Any;

/// A version of encode_to_vec that does not have a default implementation or
/// `Self: Sized` requirement. This allows us to implement this directly on the
/// type and use dynamic dispatch indirectly call `encode_to_vec` on `prost::Message`
/// and satisfy the `Sized` requirement.
#[doc(hidden)]
fn encode_to_vec2(&self) -> Vec<u8>;

#[doc(hidden)]
fn encoded_len(&self) -> usize {
prost::Message::encoded_len(self)
}
}

pub trait ToMessage {
fn to_message(self) -> Box<dyn Message>;
pub trait ToMessage<M: Message> {
fn to_message(self) -> M;

fn is_primitive<L: private::IsLocal>(&self) -> bool {
false
}
}

/// Impl ToMessage for all types that are already messages.
impl<M: Message> ToMessage<M> for M {
fn to_message(self) -> M {
self
}
}

/*
* Use a private module to create a private trait so we can use this on methods in
* ToMessage so that they can _only_ be implemented and called within our crate.
Expand Down

0 comments on commit e990e04

Please sign in to comment.