Skip to content

Commit

Permalink
Actually protect against deadlock in Dining Philosophers async exerci…
Browse files Browse the repository at this point in the history
…se (google#1772)

Swapping forks for one philosopher feels like cheating. It's like one of
the philosophers is facing away from the table. Or perhaps it's the only
right-handed philosopher at the table.

More importantly, there is no effective mechanism to prevent deadlocks.
Add that mechanism, it's useful for learning Rust.

The new code demonstrates let-else, drop and returning values from a
loop. `std::mem::swap` remains in the thread version of the Dining
Philosophers exercise for now.

This also fixes compilation. `left_fork` and `right_fork` had to be
`mut` in `main()` for the workaround to compile.
  • Loading branch information
proski authored Jan 31, 2024
1 parent 88dd268 commit fbc6be4
Showing 1 changed file with 21 additions and 12 deletions.
33 changes: 21 additions & 12 deletions src/exercises/concurrency/dining-philosophers-async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,28 @@ impl Philosopher {

// ANCHOR: Philosopher-eat
async fn eat(&self) {
// Pick up forks...
// Keep trying until we have both 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;
let (_left_fork, _right_fork) = loop {
// Pick up forks...
let left_fork = self.left_fork.try_lock();
let right_fork = self.right_fork.try_lock();
let Ok(left_fork) = left_fork else {
// If we didn't get the left fork, drop the right fork if we
// have it and let other tasks make progress.
drop(right_fork);
time::sleep(time::Duration::from_millis(1)).await;
continue;
};
let Ok(right_fork) = right_fork else {
// If we didn't get the right fork, drop the left fork and let
// other tasks make progress.
drop(left_fork);
time::sleep(time::Duration::from_millis(1)).await;
continue;
};
break (left_fork, right_fork);
};

// ANCHOR: Philosopher-eat-body
println!("{} is eating...", &self.name);
Expand Down Expand Up @@ -76,12 +91,6 @@ async fn main() {
for (i, name) in PHILOSOPHERS.iter().enumerate() {
let left_fork = Arc::clone(&forks[i]);
let right_fork = Arc::clone(&forks[(i + 1) % PHILOSOPHERS.len()]);
// To avoid a deadlock, we have to break the symmetry
// somewhere. This will swap the forks without deinitializing
// either of them.
if i == 0 {
std::mem::swap(&mut left_fork, &mut right_fork);
}
philosophers.push(Philosopher {
name: name.to_string(),
left_fork,
Expand Down

0 comments on commit fbc6be4

Please sign in to comment.