From 11571d0d1a0099319ea1d26b608d1be6fbfb20e5 Mon Sep 17 00:00:00 2001
From: rbehjati <razieh@google.com>
Date: Tue, 9 May 2023 17:34:47 +0100
Subject: [PATCH] Add dining philosophers as an async exercise (#624)

* Adds dining philosophers as an async exercise
* Adds a solution for async dining philosophers
* Adds a solution page for the afternoon session on concurrency
---
 src/SUMMARY.md                                |   5 +-
 src/exercises/concurrency/afternoon.md        |  17 +++
 .../concurrency/dining-philosophers-async.md  |  57 ++++++++++
 .../concurrency/dining-philosophers-async.rs  | 103 ++++++++++++++++++
 .../concurrency/solutions-afternoon.md        |  10 ++
 5 files changed, 191 insertions(+), 1 deletion(-)
 create mode 100644 src/exercises/concurrency/afternoon.md
 create mode 100644 src/exercises/concurrency/dining-philosophers-async.md
 create mode 100644 src/exercises/concurrency/dining-philosophers-async.rs
 create mode 100644 src/exercises/concurrency/solutions-afternoon.md

diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index c2b73649..d04259e6 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -290,7 +290,9 @@
   - [Blocking the Executor](async/pitfalls/blocking-executor.md)
   - [Pin](async/pitfalls/pin.md)
   - [Async Traits](async/pitfalls/async-traits.md)
-- [Exercises](exercises/concurrency/elevator.md)
+- [Exercises](exercises/concurrency/afternoon.md)
+  - [Dining Philosophers](exercises/concurrency/dining-philosophers-async.md)
+  - [Elevator Operations](exercises/concurrency/elevator.md)
 
 
 # Final Words
@@ -316,3 +318,4 @@
   - [Bare Metal Rust Morning](exercises/bare-metal/solutions-morning.md)
   - [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md)
   - [Concurrency Morning](exercises/concurrency/solutions-morning.md)
+  - [Concurrency Afternoon](exercises/concurrency/solutions-afternoon.md)
diff --git a/src/exercises/concurrency/afternoon.md b/src/exercises/concurrency/afternoon.md
new file mode 100644
index 00000000..3267a957
--- /dev/null
+++ b/src/exercises/concurrency/afternoon.md
@@ -0,0 +1,17 @@
+# Exercises
+
+To practice your Async Rust skills, we have again two exercises for you:
+
+* Dining philosophers: we already saw this problem in the morning. This time
+  you are going to implement it with Async Rust.
+
+* The Elevator Problem: this is a larger project that allows you experiment
+  with more advanced Async Rust features and some of its pitfalls!
+
+<details>
+
+After looking at the exercises, you can look at the [solutions] provided.
+
+[solutions]: solutions-afternoon.md
+
+</details>
diff --git a/src/exercises/concurrency/dining-philosophers-async.md b/src/exercises/concurrency/dining-philosophers-async.md
new file mode 100644
index 00000000..64bf9577
--- /dev/null
+++ b/src/exercises/concurrency/dining-philosophers-async.md
@@ -0,0 +1,57 @@
+# Dining Philosophers - Async
+
+See [dining philosophers](dining-philosophers.md) for a description of the
+problem.
+
+As before, you will need a local
+[Cargo installation](../../cargo/running-locally.md) for this exercise. Copy
+the code below to a file called `src/main.rs`, fill out the blanks, and test
+that `cargo run` does not deadlock:
+
+<!-- File src/main.rs -->
+
+```rust,compile_fail
+{{#include dining-philosophers-async.rs:Philosopher}}
+    // left_fork: ...
+    // right_fork: ...
+    // thoughts: ...
+}
+
+{{#include dining-philosophers-async.rs:Philosopher-think}}
+
+{{#include dining-philosophers-async.rs:Philosopher-eat}}
+{{#include dining-philosophers-async.rs:Philosopher-eat-body}}
+{{#include dining-philosophers-async.rs:Philosopher-eat-end}}
+    // Create forks
+
+    // Create philosophers
+
+    // Make them think and eat
+
+    // Output their thoughts
+}
+```
+
+Since this time you are using Async Rust, you'll need a `tokio` dependency.
+You can use the following `Cargo.toml`:
+
+<!-- File Cargo.toml -->
+
+```toml
+[package]
+name = "dining-philosophers-async-dine"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+tokio = {version = "1.26.0", features = ["sync", "time", "macros", "rt-multi-thread"]}
+```
+
+Also note that this time you have to use the `Mutex` and the `mpsc` module
+form the `tokio` crate.
+
+<details>
+
+* Can you make your implementation single-threaded? 
+
+</details>
diff --git a/src/exercises/concurrency/dining-philosophers-async.rs b/src/exercises/concurrency/dining-philosophers-async.rs
new file mode 100644
index 00000000..f5a31765
--- /dev/null
+++ b/src/exercises/concurrency/dining-philosophers-async.rs
@@ -0,0 +1,103 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ANCHOR: Philosopher
+use std::sync::Arc;
+use tokio::time;
+use tokio::sync::mpsc::{self, Sender};
+use tokio::sync::Mutex;
+
+struct Fork;
+
+struct Philosopher {
+    name: String,
+    // ANCHOR_END: Philosopher
+    left_fork: Arc<Mutex<Fork>>,
+    right_fork: Arc<Mutex<Fork>>,
+    thoughts: Sender<String>,
+}
+
+// ANCHOR: Philosopher-think
+impl Philosopher {
+    async fn think(&self) {
+        self.thoughts
+            .send(format!("Eureka! {} has a new idea!", &self.name)).await
+            .unwrap();
+    }
+    // ANCHOR_END: Philosopher-think
+
+    // ANCHOR: Philosopher-eat
+    async fn eat(&self) {
+        // Pick up forks...
+        // ANCHOR_END: Philosopher-eat
+        let _first_lock = self.left_fork.lock().await;
+        // Add a delay before picking the second fork to allow the execution
+        // to transfer to another task
+        time::sleep(time::Duration::from_millis(1)).await;
+        let _second_lock = self.right_fork.lock().await;
+
+        // ANCHOR: Philosopher-eat-body
+        println!("{} is eating...", &self.name);
+        time::sleep(time::Duration::from_millis(5)).await;
+        // ANCHOR_END: Philosopher-eat-body
+
+        // The locks are dropped here
+        // ANCHOR: Philosopher-eat-end
+    }
+}
+
+static PHILOSOPHERS: &[&str] =
+    &["Socrates", "Plato", "Aristotle", "Thales", "Pythagoras"];
+
+#[tokio::main]
+async fn main() {
+    // ANCHOR_END: Philosopher-eat-end
+    // Create forks
+    let mut forks = vec![];
+    (0..PHILOSOPHERS.len()).for_each(|_| forks.push(Arc::new(Mutex::new(Fork))));
+
+    // Create philosophers
+    let (philosophers, mut rx) = {
+        let mut philosophers = vec![];
+        let (tx, rx) = mpsc::channel(10);
+        for (i, name) in PHILOSOPHERS.iter().enumerate() {
+            let left_fork = forks[i].clone();
+            let right_fork = forks[(i + 1) % PHILOSOPHERS.len()].clone();
+            philosophers.push(Philosopher {
+                name: name.to_string(),
+                left_fork: if i % 2 == 0 { left_fork.clone() } else { right_fork.clone() },
+                right_fork: if i % 2 == 0 { right_fork } else { left_fork },
+                thoughts: tx.clone(),
+            });
+        }
+        (philosophers, rx)
+        // tx is dropped here, so we don't need to explicitly drop it later
+    };
+
+    // Make them think and eat
+    for phil in philosophers {
+        tokio::spawn(async move {
+            for _ in 0..100 {
+                phil.think().await;
+                phil.eat().await;
+            }
+        });
+
+    }
+
+    // Output their thoughts
+    while let Some(thought) = rx.recv().await {
+        println!("Here is a thought: {thought}");
+    }
+}
diff --git a/src/exercises/concurrency/solutions-afternoon.md b/src/exercises/concurrency/solutions-afternoon.md
new file mode 100644
index 00000000..52d57bf4
--- /dev/null
+++ b/src/exercises/concurrency/solutions-afternoon.md
@@ -0,0 +1,10 @@
+# Concurrency Afternoon Exercise
+
+## Dining Philosophers - Async
+
+([back to exercise](dining-philosophers-async.md))
+
+```rust,compile_fail
+{{#include dining-philosophers-async.rs}}
+```
+