Skip to content

Commit

Permalink
support sending ActorAddress objects (#67)
Browse files Browse the repository at this point in the history
### Summary

More or less what the title says. This PR adds the proto messages and
necessary trait implementations to send an `ActorAddress` (or a slice of
them) as a `busan::message::Message`.

In theory this means they can be serialized and shared across process
boundaries, but in reality that would require a bit more work as nothing
other than the "local" scheme is supported at the moment.

### Motivation

Sharing addresses unlocks more dynamic messaging patterns as currently
it's only possible to send messages between parents and children.

### Test Plan

Nothing ATM. Work on load balancing will make use of and test this
further.
  • Loading branch information
JohnMurray committed May 19, 2023
1 parent dc12417 commit 4fd3673
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 23 deletions.
5 changes: 4 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::io::Result;

fn main() -> Result<()> {
prost_build::compile_protos(&["src/message/wrappers.proto"], &["src/"])?;
prost_build::compile_protos(
&["src/message/wrappers.proto", "src/actor/address.proto"],
&["src/"],
)?;
Ok(())
}
24 changes: 24 additions & 0 deletions src/actor/address.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";

package actor.proto;

enum Scheme {
LOCAL = 0;

// Remote is currently a placeholder for future use.
// See busan::actor::address::UriScheme
REMOTE = 1;
}

// ActorAddress is the serializable representation of an actor address. It can be used
// to send an address to another actor for the purpose of discovery and message routing.
message ActorAddress {
Scheme scheme = 1;
string path = 2;
}

// AddressList is a simple container for holding a list of ActorAddress messages.
// This allows for multiple addresses to be sent as a single message from/to an actor.
message AddressList {
repeated ActorAddress addresses = 1;
}
38 changes: 16 additions & 22 deletions src/actor/address.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::actor::proto::Scheme;
use crate::actor::{Letter, Mailbox};
use crate::message::Message;
use log::trace;
Expand Down Expand Up @@ -45,7 +46,7 @@ impl ActorAddress {

pub(crate) fn new_root(name: &str) -> Self {
Self {
uri: Uri::new(UriScheme::Local, &[name]),
uri: Uri::new(Scheme::Local, &[name]),
mailbox: RefCell::new(None),
}
}
Expand Down Expand Up @@ -80,18 +81,6 @@ impl ActorAddress {
}
}

/// `UriScheme` is the transport mechanism for messages sent between actor systems. Messages
/// that stay within the current actor system will all have a `Local` scheme.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum UriScheme {
/// `Local` is the default scheme for messages that stay within the current actor system.
Local,

/// `Remote` is currently a placeholder value since remote is not currently implemented.
#[allow(dead_code)]
Remote,
}

/// `Uri` is a URI-like type that identifies an actor system and an actor within that system.
/// The hierarchical nature, or tree-like, organization of actors is also present in URIs, with
/// children and parents readily identifiable by path. Take for example the following hierarchy
Expand All @@ -112,12 +101,12 @@ pub(crate) enum UriScheme {
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Uri {
scheme: UriScheme,
path_segments: Vec<String>,
pub(crate) scheme: Scheme,
pub(crate) path_segments: Vec<String>,
}

impl Uri {
fn new(scheme: UriScheme, path_segments: &[&str]) -> Self {
fn new(scheme: Scheme, path_segments: &[&str]) -> Self {
if path_segments.is_empty() {
panic!("Uri must have at least one path segment");
}
Expand Down Expand Up @@ -160,13 +149,18 @@ impl Uri {
fn is_parent(&self, maybe_parent: &Self) -> bool {
maybe_parent.is_child(self)
}

/// Return the path component of the [`Uri`] as a string.
pub(crate) fn path(&self) -> String {
self.path_segments.join("/")
}
}

impl Display for Uri {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.scheme {
UriScheme::Local => write!(f, "local://")?,
UriScheme::Remote => write!(f, "remote://")?,
Scheme::Local => write!(f, "local://")?,
Scheme::Remote => write!(f, "remote://")?,
}
write!(f, "{}", self.path_segments.join("/"))
}
Expand All @@ -180,13 +174,13 @@ mod tests {
#[should_panic]
fn test_construction() {
// Test that an empty path is not allowed
Uri::new(UriScheme::Local, &[]);
Uri::new(Scheme::Local, &[]);
}

#[test]
fn test_child_construction() {
// Create a child from a root path
let root = Uri::new(UriScheme::Local, &["root"]);
let root = Uri::new(Scheme::Local, &["root"]);
let child = root.new_child("child");

// Test the relationships between the two
Expand All @@ -207,7 +201,7 @@ mod tests {

#[test]
fn test_self_reference() {
let path = Uri::new(UriScheme::Local, &["root", "some", "path"]);
let path = Uri::new(Scheme::Local, &["root", "some", "path"]);
assert_eq!(path.is_child(&path), false);
assert_eq!(path.is_parent(&path), false);
}
Expand Down Expand Up @@ -258,7 +252,7 @@ mod tests {
),
];
for (path_segments, expected) in test_cases {
let uri = Uri::new(UriScheme::Local, &path_segments);
let uri = Uri::new(Scheme::Local, &path_segments);
assert_eq!(uri.to_string(), expected);
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/actor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ pub mod address;
#[doc(hidden)]
pub mod letter;

pub mod proto;

#[doc(inline)]
pub use actor::*;
#[doc(inline)]
Expand Down
54 changes: 54 additions & 0 deletions src/actor/proto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Serializable message types for the actor module
//!
//! This module contains the protobuf definitions that map to internal types
//! such as [`ActorAddress`](crate::actor::ActorAddress) as well as the associated
//! [`ToMessage`](crate::message::ToMessage) implementations for these types.

use crate::actor;
use crate::message::common_types::impl_busan_message;
use crate::message::{Message, ToMessage};
use std::cell::RefCell;

// Import the generated protobuf definitions (see build.rs)
include!(concat!(env!("OUT_DIR"), "/actor.proto.rs"));

impl_busan_message!(ActorAddress);
impl_busan_message!(AddressList);

impl ToMessage<ActorAddress> for &actor::ActorAddress {
fn to_message(self) -> ActorAddress {
ActorAddress {
scheme: match self.uri.scheme {
Scheme::Local => Scheme::Local as i32,
Scheme::Remote => Scheme::Remote as i32,
},
path: self.uri.path(),
}
}
}

impl TryFrom<ActorAddress> for actor::ActorAddress {
type Error = String;

fn try_from(address: ActorAddress) -> Result<Self, Self::Error> {
let scheme = Scheme::from_i32(address.scheme)
.ok_or(format!("Invalid scheme: {}", address.scheme))?;
Ok(actor::ActorAddress {
// TODO: This needs an internal constructor. Presumably there is at least
// one other place we're doing this same thing
uri: actor::Uri {
scheme,
path_segments: address.path.split('/').map(|s| s.to_string()).collect(),
},
mailbox: RefCell::new(None),
})
}
}

impl ToMessage<AddressList> for &[actor::ActorAddress] {
fn to_message(self) -> AddressList {
AddressList {
addresses: self.iter().map(|a| a.to_message()).collect(),
}
}
}
1 change: 1 addition & 0 deletions src/message/common_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ macro_rules! impl_busan_message {
}
};
}
pub(crate) use impl_busan_message;

impl_busan_message!(U32Wrapper);
impl_busan_message!(U64Wrapper);
Expand Down

0 comments on commit 4fd3673

Please sign in to comment.