Skip to content

Commit

Permalink
refactor TransportConfig with a separate builder struct
Browse files Browse the repository at this point in the history
  • Loading branch information
shumbo committed Oct 1, 2023
1 parent 34e44dd commit e75b8f1
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 48 deletions.
23 changes: 13 additions & 10 deletions chorus_book/src/guide-transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,23 @@ let mut handles: Vec<thread::JoinHandle<()>> = Vec::new();

The `http` transport is used to execute choreographies on different machines. This is useful for executing choreographies in a distributed system.

To use the `http` transport, import the `HttpTransport` struct and the `HttpTransportConfig` type alias from the `chorus_lib` crate.
To use the `http` transport, import `HttpTransport` and `HttpTransportConfigBuilder` from the `chorus_lib` crate.

```rust
# extern crate chorus_lib;
use chorus_lib::transport::http::{HttpTransport, HttpTransportConfig};
use chorus_lib::transport::http::{HttpTransport, HttpTransportConfigBuilder};
```

The primary constructor requires an argument of type `HttpTransportConfig`. To create an instance of this configuration, start with `HttpTransportConfig::for_target(target_location, (hostname, port))`. It will create set a projection target and the hostname and port to listen on. Then, provide information to connect to other locations by method-chaining the `.with(other_location, (hostname, port))` method. You can think of `HttpTransportConfig` as a mapping from locations to their hostnames and ports.
We need to construct a `HttpTransportConfig` using the `HttpTransportConfigBuilder`. First, we specify the target location and the hostname and port to listen on using the `for_target` method. Then, we specify the other locations and their `(hostname, port)` pairs using the `with` method.

```rust
{{#include ./header.txt}}
# use chorus_lib::transport::http::{HttpTransport, HttpTransportConfig};
let config = HttpTransportConfig::for_target(Alice, ("localhost".to_string(), 8080))
.with(Bob, ("localhost".to_string(), 8081));

# use chorus_lib::transport::http::{HttpTransport, HttpTransportConfigBuilder};
// `Alice` listens on port 8080 on localhost
let config = HttpTransportConfigBuilder::for_target(Alice, ("localhost".to_string(), 8080))
// Connect to `Bob` on port 8081 on localhost
.with(Bob, ("localhost".to_string(), 8081))
.build();
let transport = HttpTransport::new(config);
```

Expand All @@ -113,10 +115,11 @@ You can also create your own transport by implementing the `Transport` trait. It

```rust
{{#include ./header.txt}}
# use chorus_lib::transport::TransportConfig;
let config = TransportConfig::for_target(Alice, ())
# use chorus_lib::transport::TransportConfigBuilder;
let config = TransportConfigBuilder::for_target(Alice, ())
.with(Bob, ("localhost".to_string(), 8081))
.with(Carol, ("localhost".to_string(), 8082));
.with(Carol, ("localhost".to_string(), 8082))
.build();
```

See the API documentation for more details.
Expand Down
13 changes: 7 additions & 6 deletions chorus_lib/examples/tic-tac-toe.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
/// Choreographic tik-tak-toe game
extern crate chorus_lib;

use chorus_lib::transport::http::HttpTransportConfig;
use chorus_lib::{
core::{
ChoreoOp, Choreography, ChoreographyLocation, Deserialize, Located, LocationSet, Projector,
Serialize,
},
transport::http::HttpTransport,
transport::http::{HttpTransport, HttpTransportConfigBuilder},
};

use clap::Parser;
Expand Down Expand Up @@ -295,7 +294,7 @@ fn main() {

match args.player {
'X' => {
let config = HttpTransportConfig::for_target(
let config = HttpTransportConfigBuilder::for_target(
PlayerX,
(args.hostname.as_str().to_string(), args.port),
)
Expand All @@ -305,7 +304,8 @@ fn main() {
args.opponent_hostname.as_str().to_string(),
args.opponent_port,
),
);
)
.build();

let transport = HttpTransport::new(config);
let projector = Projector::new(PlayerX, transport);
Expand All @@ -315,7 +315,7 @@ fn main() {
});
}
'O' => {
let config = HttpTransportConfig::for_target(
let config = HttpTransportConfigBuilder::for_target(
PlayerO,
(args.hostname.as_str().to_string(), args.port),
)
Expand All @@ -325,7 +325,8 @@ fn main() {
args.opponent_hostname.as_str().to_string(),
args.opponent_port,
),
);
)
.build();

let transport = HttpTransport::new(config);
let projector = Projector::new(PlayerO, transport);
Expand Down
76 changes: 56 additions & 20 deletions chorus_lib/src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,80 @@ use std::marker::PhantomData;

/// A generic struct for configuration of `Transport`.
#[derive(Clone)]
pub struct TransportConfig<L: HList, InfoType, TargetLocation: ChoreographyLocation, TargetInfoType>
{
pub struct TransportConfig<Target: ChoreographyLocation, TargetInfo, L: HList, Info> {
/// The information about locations
info: HashMap<String, InfoType>,
info: HashMap<String, Info>,
/// The information about the target choreography
target_info: (TargetLocation, TargetInfoType),
target_info: (Target, TargetInfo),
/// The struct is parametrized by the location set (`L`).
location_set: PhantomData<L>,
}

impl<InfoType, TargetLocation: ChoreographyLocation, TargetInfoType>
TransportConfig<LocationSet!(TargetLocation), InfoType, TargetLocation, TargetInfoType>
/// A builder for `TransportConfig`.
///
/// Use this builder to create a `TransportConfig` instance.
///
/// # Examples
///
/// ```
/// use chorus_lib::core::{LocationSet, ChoreographyLocation};
/// use chorus_lib::transport::TransportConfigBuilder;
///
/// #[derive(ChoreographyLocation)]
/// struct Alice;
///
/// #[derive(ChoreographyLocation)]
/// struct Bob;
///
/// let transport_config = TransportConfigBuilder::for_target(Alice, "value_for_target".to_string())
/// .with(Bob, "value_for_bob".to_string())
/// .build();
/// ```
pub struct TransportConfigBuilder<Target: ChoreographyLocation, TargetInfo, L: HList, Info> {
target: (Target, TargetInfo),
location_set: PhantomData<L>,
info: HashMap<&'static str, Info>,
}

impl<Target: ChoreographyLocation, TargetInfo, Info>
TransportConfigBuilder<Target, TargetInfo, LocationSet!(Target), Info>
{
/// A transport for a given target.
pub fn for_target(location: TargetLocation, info: TargetInfoType) -> Self {
/// Creates a new `TransportConfigBuilder` instance for a given target.
pub fn for_target(target: Target, info: TargetInfo) -> Self {
Self {
info: HashMap::new(),
target_info: (location, info),
target: (target, info),
location_set: PhantomData,
info: HashMap::new(),
}
}
}

impl<L: HList, InfoType, TargetLocation: ChoreographyLocation, TargetInfoType>
TransportConfig<L, InfoType, TargetLocation, TargetInfoType>
impl<Target: ChoreographyLocation, TargetInfo, L: HList, Info>
TransportConfigBuilder<Target, TargetInfo, L, Info>
{
/// Adds information about a new `ChoreographyLocation`.
///
/// This method tells the builder that the choreography involves a new location and how to communicate with it.
pub fn with<NewLocation: ChoreographyLocation>(
mut self,
_location: NewLocation,
info: InfoType,
) -> TransportConfig<HCons<NewLocation, L>, InfoType, TargetLocation, TargetInfoType>
where {
self.info.insert(NewLocation::name().to_string(), info);
self,
location: NewLocation,
info: Info,
) -> TransportConfigBuilder<Target, TargetInfo, HCons<NewLocation, L>, Info> {
_ = location;
let mut new_info = self.info;
new_info.insert(NewLocation::name(), info);
TransportConfigBuilder {
target: self.target,
location_set: PhantomData,
info: new_info,
}
}

/// Builds a `TransportConfig` instance.
pub fn build(self) -> TransportConfig<Target, TargetInfo, L, Info> {
TransportConfig {
info: self.info,
target_info: self.target_info,
info: HashMap::new(),
target_info: self.target,
location_set: PhantomData,
}
}
Expand Down
51 changes: 39 additions & 12 deletions chorus_lib/src/transport/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,37 @@ use retry::{
use tiny_http::Server;
use ureq::{Agent, AgentBuilder};

use crate::transport::TransportConfig;

use crate::{
core::{ChoreographyLocation, HList, Member, Portable, Transport},
transport::{TransportConfig, TransportConfigBuilder},
utils::queue::BlockingQueue,
};

type QueueMap = HashMap<String, BlockingQueue<String>>;
/// A type alias for `TransportConfig`s used for building `HttpTransport`
pub type HttpTransportConfig<L, Target> = TransportConfig<L, (String, u16), Target, (String, u16)>;

/// Config for `HttpTransport`.
pub type HttpTransportConfig<L, Target> = TransportConfig<Target, (String, u16), L, (String, u16)>;

/// A builder for `HttpTransportConfig`.
///
/// # Examples
///
/// ```
/// # use chorus_lib::core::{LocationSet, ChoreographyLocation};
/// # use chorus_lib::transport::http::HttpTransportConfigBuilder;
/// #
/// # #[derive(ChoreographyLocation)]
/// # struct Alice;
/// #
/// # #[derive(ChoreographyLocation)]
/// # struct Bob;
/// #
/// let transport_config = HttpTransportConfigBuilder::for_target(Alice, ("0.0.0.0".to_string(), 9010))
/// .with(Bob, ("example.com".to_string(), 80))
/// .build();
/// ```
pub type HttpTransportConfigBuilder<Target, L> =
TransportConfigBuilder<Target, (String, u16), L, (String, u16)>;

/// The header name for the source location.
const HEADER_SRC: &str = "X-CHORUS-SOURCE";
Expand Down Expand Up @@ -155,8 +176,10 @@ mod tests {

let mut handles = Vec::new();
{
let config = HttpTransportConfig::for_target(Alice, ("0.0.0.0".to_string(), 9010))
.with(Bob, ("localhost".to_string(), 9011));
let config =
HttpTransportConfigBuilder::for_target(Alice, ("0.0.0.0".to_string(), 9010))
.with(Bob, ("localhost".to_string(), 9011))
.build();

handles.push(thread::spawn(move || {
wait.recv().unwrap(); // wait for Bob to start
Expand All @@ -165,8 +188,9 @@ mod tests {
}));
}
{
let config = HttpTransportConfig::for_target(Bob, ("0.0.0.0".to_string(), 9011))
.with(Alice, ("localhost".to_string(), 9010));
let config = HttpTransportConfigBuilder::for_target(Bob, ("0.0.0.0".to_string(), 9011))
.with(Alice, ("localhost".to_string(), 9010))
.build();

handles.push(thread::spawn(move || {
let transport = HttpTransport::new(config);
Expand All @@ -187,8 +211,10 @@ mod tests {

let mut handles = Vec::new();
{
let config = HttpTransportConfig::for_target(Alice, ("0.0.0.0".to_string(), 9020))
.with(Bob, ("localhost".to_string(), 9021));
let config =
HttpTransportConfigBuilder::for_target(Alice, ("0.0.0.0".to_string(), 9020))
.with(Bob, ("localhost".to_string(), 9021))
.build();

handles.push(thread::spawn(move || {
signal.send(()).unwrap();
Expand All @@ -197,8 +223,9 @@ mod tests {
}));
}
{
let config = HttpTransportConfig::for_target(Bob, ("0.0.0.0".to_string(), 9021))
.with(Alice, ("localhost".to_string(), 9020));
let config = HttpTransportConfigBuilder::for_target(Bob, ("0.0.0.0".to_string(), 9021))
.with(Alice, ("localhost".to_string(), 9020))
.build();

handles.push(thread::spawn(move || {
// wait for Alice to start, which forces Alice to retry
Expand Down

0 comments on commit e75b8f1

Please sign in to comment.