Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ jobs:
- uses: peaceiris/actions-mdbook@v1
- run: mdbook build chorus_book
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
uses: actions/upload-pages-artifact@v3
with:
path: ./chorus_book/book
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

publish-crates:
runs-on: ubuntu-22.04
Expand Down
2 changes: 1 addition & 1 deletion chorus_book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
- [Runner](./guide-runner.md)
- [Higher-order Choreography](./guide-higher-order-choreography.md)
- [Location Polymorphism](./guide-location-polymorphism.md)
- [Efficient Conditionals with Enclaves and MLVs](./guide-efficient-conditionals.md)
- [Efficient Conditionals with Conclaves and MLVs](./guide-efficient-conditionals.md)
- [Links](./links.md)
52 changes: 26 additions & 26 deletions chorus_book/src/guide-efficient-conditionals.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Efficient Conditionals with Enclaves and MLVs
# Efficient Conditionals with Conclaves and MLVs

## `broadcast` incurs unnecessary communication

In [the previous section](./guide-choreography.html#broadcast), we discussed how the `broadcast` operator can be used to implement a conditional behavior in a choreography. In short, the `broadcast` operator sends a located value from a source location to all other locations, making the value available at all locations. The resulting value is a normal (not `Located`) value and it can be used to make a branch.

However, the `broadcast` operator can incur unnecessary communication when not all locations need to receive the value. Consider a simple key-value store where a *client* sends either a `Get` or `Put` request to a *primary* server, and the primary server forwards the request to a *backup* server if the request is a `Put`. The backup server does not need to receive the request if the request is a `Get`.
However, the `broadcast` operator can incur unnecessary communication when not all locations need to receive the value. Consider a simple key-value store where a _client_ sends either a `Get` or `Put` request to a _primary_ server, and the primary server forwards the request to a _backup_ server if the request is a `Put`. The backup server does not need to receive the request if the request is a `Get`.

Using the `broadcast` operator, this protocol can be implemented as follows:

Expand Down Expand Up @@ -88,12 +88,12 @@ impl Choreography<Located<Response, Client>> for KeyValueStoreChoreography {

While this implementation works, it incurs unnecessary communication. When we branch on `is_put`, we broadcast the value to all locations. This is necessary to make sure that the value is available at all locations so it can be used as a normal, non-located value. However, notice that the client does not need to receive the value. Regardless of whether the request is a `Put` or `Get`, the client should wait for the response from the primary server.

## Changing the census with `enclave`
## Changing the census with `conclave`

To avoid unnecessary communication, we can use the `enclave` operator. The `enclave` operator is similar to [the `call` operator](./guide-higher-order-choreography.html) but executes a sub-choreography only at locations that are included in its location set. Inside the sub-choreography, `broadcast` only sends the value to the locations that are included in the location set. This allows us to avoid unnecessary communication.
To avoid unnecessary communication, we can use the `conclave` operator. The `conclave` operator is similar to [the `call` operator](./guide-higher-order-choreography.html) but executes a sub-choreography only at locations that are included in its location set. Inside the sub-choreography, `broadcast` only sends the value to the locations that are included in the location set. This allows us to avoid unnecessary communication.

Let's refactor the previous example using the `conclave` operator. We define a sub-choreography `HandleRequestChoreography` that describes how the primary and backup servers (but not the client) handle the request and use the `conclave` operator to execute the sub-choreography.

Let's refactor the previous example using the `enclave` operator. We define a sub-choreography `HandleRequestChoreography` that describes how the primary and backup servers (but not the client) handle the request and use the `enclave` operator to execute the sub-choreography.

```rust
{{#include ./header.txt}}
#
Expand All @@ -109,28 +109,28 @@ Let's refactor the previous example using the `enclave` operator. We define a su
#
# #[derive(ChoreographyLocation)]
# struct Client;
#
#
# #[derive(ChoreographyLocation)]
# struct Primary;
#
#
# #[derive(ChoreographyLocation)]
# struct Backup;
#
#
# type Key = String;
# type Value = String;
#
#
# #[derive(Serialize, Deserialize)]
# enum Request {
# Get(Key),
# Put(Key, Value),
# }
#
#
# #[derive(Serialize, Deserialize)]
# enum Response {
# GetOk(Option<Value>),
# PutOk,
# }
#
#
struct HandleRequestChoreography {
request: Located<Request, Primary>,
}
Expand Down Expand Up @@ -174,7 +174,7 @@ impl Choreography<Located<Response, Client>> for KeyValueStoreChoreography {
op.comm(Client, Primary, &request_at_client);
// Execute the sub-choreography only at the primary and backup servers
let response: MultiplyLocated<Located<Response, Primary>, LocationSet!(Primary, Backup)> =
op.enclave(HandleRequestChoreography {
op.conclave(HandleRequestChoreography {
request: request_at_primary,
});
let response_at_primary: Located<Response, Primary> = response.flatten();
Expand All @@ -184,17 +184,17 @@ impl Choreography<Located<Response, Client>> for KeyValueStoreChoreography {
}
```

In this refactored version, the `HandleRequestChoreography` sub-choreography describes how the primary and backup servers handle the request. The `enclave` operator executes the sub-choreography only at the primary and backup servers. The `broadcast` operator inside the sub-choreography sends the value only to the primary and backup servers, avoiding unnecessary communication.
In this refactored version, the `HandleRequestChoreography` sub-choreography describes how the primary and backup servers handle the request. The `conclave` operator executes the sub-choreography only at the primary and backup servers. The `broadcast` operator inside the sub-choreography sends the value only to the primary and backup servers, avoiding unnecessary communication.

The `enclave` operator returns a return value of the sub-choreography wrapped as a `MultiplyLocated` value. Since `HandleRequestChoreography` returns a `Located<Response, Primary>`, the return value of the `enclave` operator is a `MultiplyLocated<Located<Response, Primary>, LocationSet!(Primary, Backup)>`. To get the located value at the primary server, we can use the `locally` operator to unwrap the `MultiplyLocated` value on the primary. Since this is a common pattern, we provide the `flatten` method on `MultiplyLocated` to simplify this operation.
The `conclave` operator returns a return value of the sub-choreography wrapped as a `MultiplyLocated` value. Since `HandleRequestChoreography` returns a `Located<Response, Primary>`, the return value of the `conclave` operator is a `MultiplyLocated<Located<Response, Primary>, LocationSet!(Primary, Backup)>`. To get the located value at the primary server, we can use the `locally` operator to unwrap the `MultiplyLocated` value on the primary. Since this is a common pattern, we provide the `flatten` method on `MultiplyLocated` to simplify this operation.

With the `enclave` operator, we can avoid unnecessary communication and improve the efficiency of the choreography.
With the `conclave` operator, we can avoid unnecessary communication and improve the efficiency of the choreography.

## Reusing Knowledge of Choice in Enclaves
## Reusing Knowledge of Choice in Conclaves

The key idea behind the `enclave` operator is that a normal value inside a choreography is equivalent to a (multiply) located value at all locations executing the choreography. This is why a normal value in a sub-choreography becomes a multiply located value at all locations executing the sub-choreography when returned from the `enclave` operator.
The key idea behind the `conclave` operator is that a normal value inside a choreography is equivalent to a (multiply) located value at all locations executing the choreography. This is why a normal value in a sub-choreography becomes a multiply located value at all locations executing the sub-choreography when returned from the `conclave` operator.

It is possible to perform this conversion in the opposite direction as well. If we have a multiply located value at some locations, and those are the only locations executing the choreography, then we can obtain a normal value out of the multiply located value. This is useful when we want to reuse the already known information about a choice in an enclave.
It is possible to perform this conversion in the opposite direction as well. If we have a multiply located value at some locations, and those are the only locations executing the choreography, then we can obtain a normal value out of the multiply located value. This is useful when we want to reuse the already known information about a choice in a conclave.

Inside a choreography, we can use the `naked` operator to convert a multiply located value at locations `S` to a normal value if the census of the choreography is a subset of `S`.

Expand All @@ -215,28 +215,28 @@ For example, the above choreography can be written as follows:
#
# #[derive(ChoreographyLocation)]
# struct Client;
#
#
# #[derive(ChoreographyLocation)]
# struct Primary;
#
#
# #[derive(ChoreographyLocation)]
# struct Backup;
#
#
# type Key = String;
# type Value = String;
#
#
# #[derive(Serialize, Deserialize)]
# enum Request {
# Get(Key),
# Put(Key, Value),
# }
#
#
# #[derive(Serialize, Deserialize)]
# enum Response {
# GetOk(Option<Value>),
# PutOk,
# }
#
#
struct HandleRequestChoreography {
request: Located<Request, Primary>,
is_put: MultiplyLocated<bool, LocationSet!(Primary, Backup)>,
Expand Down Expand Up @@ -288,7 +288,7 @@ impl Choreography<Located<Response, Client>> for KeyValueStoreChoreography {
&is_put_at_primary,
);
let response: MultiplyLocated<Located<Response, Primary>, LocationSet!(Primary, Backup)> =
op.enclave(HandleRequestChoreography {
op.conclave(HandleRequestChoreography {
is_put,
request: request_at_primary,
});
Expand Down
2 changes: 1 addition & 1 deletion chorus_book/src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ At high level, ChoRus provides the following features:
- Passing located arguments to / receiving located return values from choreographies.
- Location polymorphism.
- Higher-order choreographies.
- Efficient conditional with choreographic enclaves.
- Efficient conditional with choreographic conclaves.
- Performing end-point projection.
- Pluggable message transports.
- Two built-in transports: `Local` and `HTTP`.
Expand Down
4 changes: 2 additions & 2 deletions chorus_lib/examples/bookseller2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl<D: Choreography<Located<bool, Buyer1>, L = LocationSet!(Buyer1, Buyer2)> +
return i32::MAX;
});
let price_at_buyer1 = op.comm(Seller, Buyer1, &price_at_seller);
let decision_at_buyer1 = op.enclave(D::new(price_at_buyer1)).flatten();
let decision_at_buyer1 = op.conclave(D::new(price_at_buyer1)).flatten();

struct GetDeliveryDateChoreography {
inventory: Located<Inventory, Seller>,
Expand All @@ -124,7 +124,7 @@ impl<D: Choreography<Located<bool, Buyer1>, L = LocationSet!(Buyer1, Buyer2)> +
}

return op
.enclave(GetDeliveryDateChoreography {
.conclave(GetDeliveryDateChoreography {
inventory: self.inventory.clone(),
title_at_seller: title_at_seller.clone(),
decision_at_buyer1,
Expand Down
6 changes: 3 additions & 3 deletions chorus_lib/examples/cardgame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,11 @@ impl<
Q: Member<Self::L, QMemberL>,
Q: Member<Self::QS, QMemberQS>,
{
struct Enclave<Player: ChoreographyLocation> {
struct Conclave<Player: ChoreographyLocation> {
hand1: Located<i32, Player>,
wants_next_card: Located<bool, Player>,
}
impl<Player: ChoreographyLocation> Choreography<Located<Vec<i32>, Player>> for Enclave<Player> {
impl<Player: ChoreographyLocation> Choreography<Located<Vec<i32>, Player>> for Conclave<Player> {
type L = LocationSet!(Dealer, Player);

fn run(self, op: &impl ChoreoOp<Self::L>) -> Located<Vec<i32>, Player> {
Expand All @@ -219,7 +219,7 @@ impl<
}
let hand1 = op.locally(Q::new(), |un| *un.unwrap(self.hand1));
let wants_next_card = op.locally(Q::new(), |un| *un.unwrap(self.wants_next_card));
op.enclave(Enclave::<Q> {
op.conclave(Conclave::<Q> {
hand1,
wants_next_card,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Choreography for MainChoreography {
Choice::Bob
}
});
let choice_and_query = op.enclave(ChooseQueryChoreography {
let choice_and_query = op.conclave(ChooseQueryChoreography {
alice_choice: choice,
});
let query_at_alice = op.locally(Alice, |un| {
Expand All @@ -54,7 +54,7 @@ impl Choreography for MainChoreography {
return r;
});
let response = op.broadcast(Carol, response_at_carol);
op.enclave(TerminalChoreography {
op.conclave(TerminalChoreography {
choice_and_query,
response,
});
Expand Down
2 changes: 1 addition & 1 deletion chorus_lib/examples/loc-poly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Choreography<Located<i32, Alice>> for MainChoreography {
});
let v2 = op.locally(Bob, |un| un.unwrap(&v2) + 10);
return op
.enclave(CommAndPrint {
.conclave(CommAndPrint {
sender: Bob,
receiver: Alice,
data: v2,
Expand Down
71 changes: 71 additions & 0 deletions chorus_lib/examples/playground.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/// # Testing Playground
///
/// This is a place where you can write your own code to test ChoRus.
/// Add your test cases, examples, or any experimental code here to validate
/// and understand the functionality of the library.
///
/// ## How to Run
///
/// You can run this program by using the following command:
///
/// cargo run --example playground
use chorus_lib::{
core::{Choreography, ChoreographyLocation, LocationSet},
transport::local::{LocalTransport, LocalTransportChannelBuilder},
};
use rand;

// STEP 1: Add locations
#[derive(ChoreographyLocation)]
struct Alice;

#[derive(ChoreographyLocation)]
struct Bob;

// STEP 2: Write a Choreography
struct MainChoreography;

impl Choreography for MainChoreography {
type L = LocationSet!(Alice, Bob);

fn run(self, op: &impl chorus_lib::core::ChoreoOp<Self::L>) -> () {
let random_number_at_alice = op.locally(Alice, |_| {
let random_number = rand::random::<u32>();
println!("Random number at Alice: {}", random_number);
random_number
});
let random_number_at_bob = op.comm(Alice, Bob, &random_number_at_alice);
op.locally(Bob, |un| {
let random_number = un.unwrap(&random_number_at_bob);
println!("Random number at Bob: {}", random_number);
});
}
}

// STEP 3: Run the choreography
fn main() {
// In this example, we use the local transport and run the choreography in two threads.
// Refer to the documentation for more information on how to use other transports.
let mut handles = Vec::new();
let transport_channel = LocalTransportChannelBuilder::new()
.with(Alice)
.with(Bob)
.build();
{
let transport = LocalTransport::new(Alice, transport_channel.clone());
handles.push(std::thread::spawn(move || {
let projector = chorus_lib::core::Projector::new(Alice, transport);
projector.epp_and_run(MainChoreography);
}));
}
{
let transport = LocalTransport::new(Bob, transport_channel.clone());
handles.push(std::thread::spawn(move || {
let projector = chorus_lib::core::Projector::new(Bob, transport);
projector.epp_and_run(MainChoreography);
}));
}
for h in handles {
h.join().unwrap();
}
}
2 changes: 1 addition & 1 deletion chorus_lib/examples/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl Choreography for MainChoreography {
fn run(self, op: &impl ChoreoOp<Self::L>) {
let x_at_alice = op.locally(Alice, |_| get_random_number());
let x_at_bob = op.comm(Alice, Bob, &x_at_alice);
let result = op.enclave(BobCarolChoreography { x_at_bob });
let result = op.conclave(BobCarolChoreography { x_at_bob });
op.locally(Bob, |un| {
let is_even = un.unwrap(&un.unwrap(&result).is_even_at_bob);
assert!(is_even);
Expand Down
6 changes: 3 additions & 3 deletions chorus_lib/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ pub trait ChoreoOp<ChoreoLS: LocationSet> {
M: LocationSet + Subset<ChoreoLS, Index>;

/// Calls a choreography on a subset of locations.
fn enclave<R, S: LocationSet, C: Choreography<R, L = S>, Index>(
fn conclave<R, S: LocationSet, C: Choreography<R, L = S>, Index>(
&self,
choreo: C,
) -> MultiplyLocated<R, S>
Expand Down Expand Up @@ -890,7 +890,7 @@ where
choreo.run(&op)
}

fn enclave<R, S: LocationSet, C: Choreography<R, L = S>, Index>(
fn conclave<R, S: LocationSet, C: Choreography<R, L = S>, Index>(
&self,
choreo: C,
) -> MultiplyLocated<R, S> {
Expand Down Expand Up @@ -1280,7 +1280,7 @@ impl<RunnerLS: LocationSet> Runner<RunnerLS> {
choreo.run(&op)
}

fn enclave<R, S: LocationSet, C: Choreography<R, L = S>, Index>(
fn conclave<R, S: LocationSet, C: Choreography<R, L = S>, Index>(
&self,
choreo: C,
) -> MultiplyLocated<R, S> {
Expand Down
4 changes: 2 additions & 2 deletions chorus_lib/tests/booksellers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ where
});
let price_at_buyer1 = op.comm(Seller, Buyer1, &price_at_seller);
let decider = D::new(price_at_buyer1, self.budgets);
let decision_at_buyer1 = op.enclave(decider).flatten();
let decision_at_buyer1 = op.conclave(decider).flatten();

struct GetDeliveryDateChoreography {
inventory: Located<Inventory, Seller>,
Expand Down Expand Up @@ -97,7 +97,7 @@ where

return op.broadcast(
Buyer1,
op.enclave(GetDeliveryDateChoreography {
op.conclave(GetDeliveryDateChoreography {
inventory: self.inventory.clone(),
title_at_seller: title_at_seller.clone(),
decision_at_buyer1,
Expand Down
2 changes: 1 addition & 1 deletion chorus_lib/tests/kvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ where
fn run(self, op: &impl ChoreoOp<Self::L>) -> Located<Response, Client> {
let request = op.comm(Client, Server, &self.request);
let response = op
.enclave(HandleRequest::<Backups, _, _> {
.conclave(HandleRequest::<Backups, _, _> {
request: request,
_phantoms: PhantomData,
})
Expand Down
Loading