From 520395428d4c07818b5c2cbd4aac230233435505 Mon Sep 17 00:00:00 2001 From: Shun Kashiwa Date: Wed, 12 Mar 2025 13:42:43 -0700 Subject: [PATCH 1/3] `enclave` -> `conclave` --- chorus_book/src/SUMMARY.md | 2 +- .../src/guide-efficient-conditionals.md | 52 +++++++++---------- chorus_book/src/introduction.md | 2 +- chorus_lib/examples/bookseller2.rs | 4 +- chorus_lib/examples/cardgame.rs | 6 +-- .../{enclave-mlv.rs => conclave-mlv.rs} | 4 +- chorus_lib/examples/loc-poly.rs | 2 +- chorus_lib/examples/runner.rs | 2 +- chorus_lib/src/core.rs | 6 +-- chorus_lib/tests/booksellers.rs | 4 +- chorus_lib/tests/kvs.rs | 2 +- 11 files changed, 43 insertions(+), 43 deletions(-) rename chorus_lib/examples/{enclave-mlv.rs => conclave-mlv.rs} (97%) diff --git a/chorus_book/src/SUMMARY.md b/chorus_book/src/SUMMARY.md index a2b1f9e..71d276d 100644 --- a/chorus_book/src/SUMMARY.md +++ b/chorus_book/src/SUMMARY.md @@ -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) diff --git a/chorus_book/src/guide-efficient-conditionals.md b/chorus_book/src/guide-efficient-conditionals.md index ab56880..d110fbb 100644 --- a/chorus_book/src/guide-efficient-conditionals.md +++ b/chorus_book/src/guide-efficient-conditionals.md @@ -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: @@ -88,12 +88,12 @@ impl Choreography> 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}} # @@ -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), # PutOk, # } -# +# struct HandleRequestChoreography { request: Located, } @@ -174,7 +174,7 @@ impl Choreography> for KeyValueStoreChoreography { op.comm(Client, Primary, &request_at_client); // Execute the sub-choreography only at the primary and backup servers let response: MultiplyLocated, LocationSet!(Primary, Backup)> = - op.enclave(HandleRequestChoreography { + op.conclave(HandleRequestChoreography { request: request_at_primary, }); let response_at_primary: Located = response.flatten(); @@ -184,17 +184,17 @@ impl Choreography> 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`, the return value of the `enclave` operator is a `MultiplyLocated, 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`, the return value of the `conclave` operator is a `MultiplyLocated, 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`. @@ -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), # PutOk, # } -# +# struct HandleRequestChoreography { request: Located, is_put: MultiplyLocated, @@ -288,7 +288,7 @@ impl Choreography> for KeyValueStoreChoreography { &is_put_at_primary, ); let response: MultiplyLocated, LocationSet!(Primary, Backup)> = - op.enclave(HandleRequestChoreography { + op.conclave(HandleRequestChoreography { is_put, request: request_at_primary, }); diff --git a/chorus_book/src/introduction.md b/chorus_book/src/introduction.md index d954bf7..11a7a1c 100644 --- a/chorus_book/src/introduction.md +++ b/chorus_book/src/introduction.md @@ -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`. diff --git a/chorus_lib/examples/bookseller2.rs b/chorus_lib/examples/bookseller2.rs index 0f97107..c1213e4 100644 --- a/chorus_lib/examples/bookseller2.rs +++ b/chorus_lib/examples/bookseller2.rs @@ -97,7 +97,7 @@ impl, 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, @@ -124,7 +124,7 @@ impl, L = LocationSet!(Buyer1, Buyer2)> + } return op - .enclave(GetDeliveryDateChoreography { + .conclave(GetDeliveryDateChoreography { inventory: self.inventory.clone(), title_at_seller: title_at_seller.clone(), decision_at_buyer1, diff --git a/chorus_lib/examples/cardgame.rs b/chorus_lib/examples/cardgame.rs index a5a6746..f0277b1 100644 --- a/chorus_lib/examples/cardgame.rs +++ b/chorus_lib/examples/cardgame.rs @@ -193,11 +193,11 @@ impl< Q: Member, Q: Member, { - struct Enclave { + struct Conclave { hand1: Located, wants_next_card: Located, } - impl Choreography, Player>> for Enclave { + impl Choreography, Player>> for Conclave { type L = LocationSet!(Dealer, Player); fn run(self, op: &impl ChoreoOp) -> Located, Player> { @@ -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:: { + op.conclave(Conclave:: { hand1, wants_next_card, }) diff --git a/chorus_lib/examples/enclave-mlv.rs b/chorus_lib/examples/conclave-mlv.rs similarity index 97% rename from chorus_lib/examples/enclave-mlv.rs rename to chorus_lib/examples/conclave-mlv.rs index 408c337..70269c6 100644 --- a/chorus_lib/examples/enclave-mlv.rs +++ b/chorus_lib/examples/conclave-mlv.rs @@ -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| { @@ -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, }); diff --git a/chorus_lib/examples/loc-poly.rs b/chorus_lib/examples/loc-poly.rs index b6d3e67..b5a62ee 100644 --- a/chorus_lib/examples/loc-poly.rs +++ b/chorus_lib/examples/loc-poly.rs @@ -48,7 +48,7 @@ impl Choreography> for MainChoreography { }); let v2 = op.locally(Bob, |un| un.unwrap(&v2) + 10); return op - .enclave(CommAndPrint { + .conclave(CommAndPrint { sender: Bob, receiver: Alice, data: v2, diff --git a/chorus_lib/examples/runner.rs b/chorus_lib/examples/runner.rs index b8adb18..71c0c6d 100644 --- a/chorus_lib/examples/runner.rs +++ b/chorus_lib/examples/runner.rs @@ -52,7 +52,7 @@ impl Choreography for MainChoreography { fn run(self, op: &impl ChoreoOp) { 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); diff --git a/chorus_lib/src/core.rs b/chorus_lib/src/core.rs index 2530665..f4f4834 100644 --- a/chorus_lib/src/core.rs +++ b/chorus_lib/src/core.rs @@ -480,7 +480,7 @@ pub trait ChoreoOp { M: LocationSet + Subset; /// Calls a choreography on a subset of locations. - fn enclave, Index>( + fn conclave, Index>( &self, choreo: C, ) -> MultiplyLocated @@ -890,7 +890,7 @@ where choreo.run(&op) } - fn enclave, Index>( + fn conclave, Index>( &self, choreo: C, ) -> MultiplyLocated { @@ -1280,7 +1280,7 @@ impl Runner { choreo.run(&op) } - fn enclave, Index>( + fn conclave, Index>( &self, choreo: C, ) -> MultiplyLocated { diff --git a/chorus_lib/tests/booksellers.rs b/chorus_lib/tests/booksellers.rs index cc30349..6b3e8ed 100644 --- a/chorus_lib/tests/booksellers.rs +++ b/chorus_lib/tests/booksellers.rs @@ -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, @@ -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, diff --git a/chorus_lib/tests/kvs.rs b/chorus_lib/tests/kvs.rs index 959990d..87497cf 100644 --- a/chorus_lib/tests/kvs.rs +++ b/chorus_lib/tests/kvs.rs @@ -148,7 +148,7 @@ where fn run(self, op: &impl ChoreoOp) -> Located { let request = op.comm(Client, Server, &self.request); let response = op - .enclave(HandleRequest:: { + .conclave(HandleRequest:: { request: request, _phantoms: PhantomData, }) From a1296f4365fd690c775536b6f3306a439e381b23 Mon Sep 17 00:00:00 2001 From: Shun Kashiwa Date: Wed, 12 Mar 2025 14:01:11 -0700 Subject: [PATCH 2/3] add playground --- chorus_lib/examples/playground.rs | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 chorus_lib/examples/playground.rs diff --git a/chorus_lib/examples/playground.rs b/chorus_lib/examples/playground.rs new file mode 100644 index 0000000..a4b5bb2 --- /dev/null +++ b/chorus_lib/examples/playground.rs @@ -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) -> () { + let random_number_at_alice = op.locally(Alice, |_| { + let random_number = rand::random::(); + 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(); + } +} From bef6103dc9e30eb8e44baf939ba81c1de6a4162f Mon Sep 17 00:00:00 2001 From: Shun Kashiwa Date: Wed, 12 Mar 2025 14:06:07 -0700 Subject: [PATCH 3/3] update page actions --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0bea2eb..ee87bbc 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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