1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-06-17 22:57:35 +02:00

Cleanup references to "Day 4" (#603)

* Align outline with new spin-off course structure

With the new structure, the section on Android is a spin-off course
from the main 3-day course on Rust Fundamentals. The Bare-metal and
Concurrency days are spin-off courses in the same way.

* Explain new course structure

* Align Bare-Metal welcome page with other deep dives

* Merge Day 4 page into Course Structure page

* Remove Day 4 Welcome page

This aligns the Concurrency in Rust section with the Bare-Metal Rust
deep dive.

* Show subsections for Android deep dive

This aligns the Rust in Android section with the other deep dives.

* Clean up welcome page and README

We now cover async Rust and the course is no longer a four day course.

* Remove reference to the old Day 4

* Remove Day 4 references from exercises
This commit is contained in:
Martin Geisler
2023-05-02 08:02:28 +02:00
committed by GitHub
parent c67922ce8c
commit 3b21053ff2
27 changed files with 164 additions and 154 deletions

View File

@ -0,0 +1,50 @@
# Dining Philosophers
The dining philosophers problem is a classic problem in concurrency:
> Five philosophers dine together at the same table. Each philosopher has their
> own place at the table. There is a fork between each plate. The dish served is
> a kind of spaghetti which has to be eaten with two forks. Each philosopher can
> only alternately think and eat. Moreover, a philosopher can only eat their
> spaghetti when they have both a left and right fork. Thus two forks will only
> be available when their two nearest neighbors are thinking, not eating. After
> an individual philosopher finishes eating, they will put down both forks.
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.rs:Philosopher}}
// left_fork: ...
// right_fork: ...
// thoughts: ...
}
{{#include dining-philosophers.rs:Philosopher-think}}
{{#include dining-philosophers.rs:Philosopher-eat}}
// Pick up forks...
{{#include dining-philosophers.rs:Philosopher-eat-end}}
// Create forks
// Create philosophers
// Make them think and eat
// Output their thoughts
}
```
You can use the following `Cargo.toml`:
<!-- File Cargo.toml -->
```toml
[package]
name = "dining-philosophers"
version = "0.1.0"
edition = "2021"
```

View File

@ -0,0 +1,94 @@
// Copyright 2022 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::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;
struct Fork;
struct Philosopher {
name: String,
// ANCHOR_END: Philosopher
left_fork: Arc<Mutex<Fork>>,
right_fork: Arc<Mutex<Fork>>,
thoughts: mpsc::SyncSender<String>,
}
// ANCHOR: Philosopher-think
impl Philosopher {
fn think(&self) {
self.thoughts
.send(format!("Eureka! {} has a new idea!", &self.name))
.unwrap();
}
// ANCHOR_END: Philosopher-think
// ANCHOR: Philosopher-eat
fn eat(&self) {
// ANCHOR_END: Philosopher-eat
println!("{} is trying to eat", &self.name);
let left = self.left_fork.lock().unwrap();
let right = self.right_fork.lock().unwrap();
// ANCHOR: Philosopher-eat-end
println!("{} is eating...", &self.name);
thread::sleep(Duration::from_millis(10));
}
}
static PHILOSOPHERS: &[&str] =
&["Socrates", "Plato", "Aristotle", "Thales", "Pythagoras"];
fn main() {
// ANCHOR_END: Philosopher-eat-end
let (tx, rx) = mpsc::sync_channel(10);
let forks = (0..PHILOSOPHERS.len())
.map(|_| Arc::new(Mutex::new(Fork)))
.collect::<Vec<_>>();
for i in 0..forks.len() {
let tx = tx.clone();
let mut left_fork = forks[i].clone();
let mut right_fork = forks[(i + 1) % forks.len()].clone();
// To avoid a deadlock, we have to break the symmetry
// somewhere. This will swap the forks without deinitializing
// either of them.
if i == forks.len() - 1 {
std::mem::swap(&mut left_fork, &mut right_fork);
}
let philosopher = Philosopher {
name: PHILOSOPHERS[i].to_string(),
thoughts: tx,
left_fork,
right_fork,
};
thread::spawn(move || {
for _ in 0..100 {
philosopher.eat();
philosopher.think();
}
});
}
drop(tx);
for thought in rx {
println!("{thought}");
}
}

View File

@ -0,0 +1,91 @@
# Elevator Operation
Elevators seem simple. You press a button, doors open, you wait, and you're at
the floor you requested. But implementing an elevator controller is surprisingly
difficult! This exercise involves building a simple elevator control that
operates in a simple simulator.
The overall design of this elevator uses the actor pattern: you will implement a
controller task that communicates with other components of the elevator system
by sending and receiving messages.
## Getting Started
Download the [exercise template](../../comprehensive-rust-exercises.zip) and look in the `elevator`
directory for the following files.
`src/main.rs`:
<!-- File src/main.rs -->
```rust,compile_fail
{{#include elevator/src/main.rs}}
```
`src/building.rs`:
<!-- File src/building.rs -->
```rust,compile_fail
{{#include elevator/src/building.rs}}
```
`src/driver.rs`:
<!-- File src/driver.rs -->
```rust,compile_fail
{{#include elevator/src/driver.rs}}
```
`src/controller.rs`:
<!-- File src/controller.rs -->
```rust,compile_fail
{{#include elevator/src/controller.rs}}
```
`Cargo.toml` (you shouldn't need to change this):
<!-- File Cargo.toml -->
```toml
{{#include elevator/Cargo.toml}}
```
Use `cargo run` to run the elevator simulation.
## Exercises
Begin by implementing a controller that can transport the passengers provided by
the simple driver. There is only one elevator, and passengers always go from
floor 0 to floor 2, one-by-one.
Once you have this done, make the problem more complex. Suggested tasks:
* Make the driver more complex, with passengers arriving at random floors with
random destinations at random times.
* Create a building with more than one elevator, and adjust the controller to
handle this efficiently.
* Add additional events and metadata to analyze your controller's efficiency.
What is the distribution of wait time for passengers? Is the result fair?
* Modify the building to support a maximum passenger capacity for each
elevator, and modify the controller to take this information into account.
* Update the driver to simulate business traffic, with lots of passengers going
up from the ground floor at the same time, and those passengers returning to
the ground floor some time later. Can your controller adjust to these
circumstances?
* Modify the building to support "destination dispatch", where passengers
signal their destination floor in the elevator lobby, before boarding the
elevator.
* If you are taking the course with other students, trade controllers or
drivers with another student to see how robust your design is.
* Build a textual or graphical display of the elevators as they run.

View File

@ -0,0 +1,318 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "elevator"
version = "0.1.0"
dependencies = [
"tokio",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "2.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
dependencies = [
"autocfg",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"

View File

@ -0,0 +1,9 @@
[workspace]
[package]
name = "elevator"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.26.0", features = ["full"] }

View File

@ -0,0 +1,234 @@
//! The building simulates floors and elevators.
use tokio::sync::{broadcast, mpsc};
use tokio::task;
use tokio::time;
#[derive(Debug, Clone)]
pub enum Direction {
Up,
Down,
}
/// A passenger is a person with a destination floor in mind.
#[derive(Debug)]
struct Passenger {
destination: FloorId,
}
/// FloorId identifies a floor. These are zero-based integers.
pub type FloorId = usize;
/// Floor represents the current status of a floor in the building.
#[derive(Default, Debug)]
struct Floor {
passengers: Vec<Passenger>,
}
/// ElevatorId identifies an elevator in the building. These are zero-based integers.
pub type ElevatorId = usize;
/// Elevator represents the current status of an elevator in the building.
#[derive(Default, Debug)]
struct Elevator {
/// Floor the elevator is currently on. In the simulation the elevator
/// transports instantaneously from one floor to the next in a single
/// simulation tick.
position: FloorId,
/// Destination floor for the elevator, if any. This can change at any time.
destination: Option<FloorId>,
/// Passengers currently on the elevator.
passengers: Vec<Passenger>,
/// True if the elevator is stopped with the doors open. The elevator
/// will not move with the doors open, but they will close at the next
/// tick of the simulation.
doors_open: bool,
}
/// A BuildingEvent is an event that occurs in the building.
#[derive(Debug, Clone)]
pub enum BuildingEvent {
/// A passenger has pressed a floor button in the elevator.
FloorButtonPressed(ElevatorId, FloorId),
/// A passenger on the given floor has pressed the call button.
CallButtonPressed(FloorId, Direction),
/// The elevator has arrived at the given floor. If this is the
/// elevator's destination, then it will stop open its doors.
AtFloor(ElevatorId, FloorId),
/// A passenger has been delivered to their desired floor.
PassengerDelivered(FloorId),
}
/// A BuildingCommand tells the building what to do.
#[derive(Debug)]
pub enum BuildingCommand {
/// Set the elevator's destination. The elevator will close its doors
/// if necessary and then begin moving toward this floor.
GoToFloor(ElevatorId, FloorId),
}
/// A DriverCommand is a message from the driver to change the state of
/// the building.
#[derive(Debug)]
pub enum DriverCommand {
/// A passenger has arrived and is waiting for an elevator. The passenger will automatically
/// press the relevant call button, board the elevator when it arrives, press their floor
/// button, and depart when the doors open on their destination floor.
PassengerArrived { at: FloorId, destination: FloorId },
/// Halt all activity in the building and end the building task.
Halt,
}
/// Building manages the current status of the building.
#[derive(Debug)]
pub struct Building {
floors: Vec<Floor>,
elevators: Vec<Elevator>,
}
impl Building {
pub fn new(num_floors: usize, num_elevators: usize) -> Self {
let mut floors = vec![];
for _ in 0..num_floors {
floors.push(Floor::default());
}
let mut elevators = vec![];
for _ in 0..num_elevators {
elevators.push(Elevator::default());
}
Self { floors, elevators }
}
/// Start the building. The resulting channels are used to communicate
/// with the building
pub fn start(
self,
) -> (
task::JoinHandle<()>,
broadcast::Receiver<BuildingEvent>,
mpsc::Sender<BuildingCommand>,
mpsc::Sender<DriverCommand>,
) {
let (events_tx, events_rx) = broadcast::channel(10);
let (building_cmd_tx, building_cmd_rx) = mpsc::channel(10);
let (driver_cmd_tx, driver_cmd_rx) = mpsc::channel(10);
let task = tokio::spawn(self.run(events_tx, building_cmd_rx, driver_cmd_rx));
(task, events_rx, building_cmd_tx, driver_cmd_tx)
}
async fn run(
mut self,
events_tx: broadcast::Sender<BuildingEvent>,
mut building_cmd_rx: mpsc::Receiver<BuildingCommand>,
mut driver_cmd_rx: mpsc::Receiver<DriverCommand>,
) {
let mut ticker = time::interval(time::Duration::from_millis(100));
loop {
tokio::select! {
Some(BuildingCommand::GoToFloor(el, fl)) = building_cmd_rx.recv() => {
self.elevators[el].destination = Some(fl);
}
Some(cmd) = driver_cmd_rx.recv() => {
match cmd {
DriverCommand::PassengerArrived{at, destination} => {
self.new_passenger(&events_tx, at, destination).await;
}
DriverCommand::Halt => return,
}
}
_ = ticker.tick() => self.move_elevators(&events_tx).await
}
}
}
/// Move the elevators toward their destinations.
async fn move_elevators(&mut self, events_tx: &broadcast::Sender<BuildingEvent>) {
for el in 0..self.elevators.len() {
let elevator = &mut self.elevators[el];
// If the elevator's doors are open, close them and wait for the next tick.
if elevator.doors_open {
elevator.doors_open = false;
continue;
}
// If the elevator has somewhere to go, move toward it.
if let Some(dest) = elevator.destination {
if dest > elevator.position {
elevator.position += 1;
}
if dest < elevator.position {
elevator.position -= 1;
}
events_tx
.send(BuildingEvent::AtFloor(el, elevator.position))
.unwrap();
// If the elevator has reached its destination, open
// the doors and let passengers get on and off.
if elevator.position == dest {
elevator.destination = None;
elevator.doors_open = true;
self.exchange_passengers(&events_tx, el).await;
}
}
}
}
/// Handle a new passenger arriving at the given floor.
async fn new_passenger(
&mut self,
events_tx: &broadcast::Sender<BuildingEvent>,
at: FloorId,
destination: FloorId,
) {
println!("Passenger arrived at {} going to {}", at, destination);
if at == destination {
events_tx
.send(BuildingEvent::PassengerDelivered(destination))
.unwrap();
return;
}
self.floors[at].passengers.push(Passenger { destination });
let dir = if at < destination {
Direction::Up
} else {
Direction::Down
};
events_tx
.send(BuildingEvent::CallButtonPressed(at, dir))
.unwrap();
}
/// The doors for the given elevator are open, so take on and discharge passengers.
async fn exchange_passengers(
&mut self,
events_tx: &broadcast::Sender<BuildingEvent>,
el: ElevatorId,
) {
let elevator = &mut self.elevators[el];
let fl = elevator.position;
// Handle passengers leaving the elevator at their floor.
let (this_floor, other_floors): (Vec<Passenger>, Vec<Passenger>) = elevator
.passengers
.drain(..)
.partition(|px| px.destination == fl);
for px in this_floor {
events_tx
.send(BuildingEvent::PassengerDelivered(px.destination))
.unwrap();
}
elevator.passengers = other_floors;
// Handle passengers entering the elevator.
for px in self.floors[fl].passengers.drain(..) {
events_tx
.send(BuildingEvent::FloorButtonPressed(el, px.destination))
.unwrap();
elevator.passengers.push(px);
}
}
}

View File

@ -0,0 +1,28 @@
//! The controller directs the elevators to operate so that passengers
//! get to their destinations.
use crate::building::{BuildingCommand, BuildingEvent};
use tokio::sync::{broadcast, mpsc};
pub async fn controller(
mut events_rx: broadcast::Receiver<BuildingEvent>,
building_cmd_tx: mpsc::Sender<BuildingCommand>,
) {
while let Ok(evt) = events_rx.recv().await {
match evt {
BuildingEvent::CallButtonPressed(at, _) => {
building_cmd_tx
.send(BuildingCommand::GoToFloor(0, at))
.await
.unwrap();
}
BuildingEvent::FloorButtonPressed(_, destination) => {
building_cmd_tx
.send(BuildingCommand::GoToFloor(0, destination))
.await
.unwrap();
}
_ => {}
}
}
}

View File

@ -0,0 +1,35 @@
//! The driver controls when and where passengers arrive.
use crate::building::{Building, BuildingEvent, DriverCommand};
use tokio::sync::{broadcast, mpsc};
/// Create a new building to be driven by this driver.
pub fn make_building() -> Building {
Building::new(3, 1)
}
/// Simulate people arriving at the ground floor and going to the first floor, one by one.
pub async fn driver(
mut events_rx: broadcast::Receiver<BuildingEvent>,
driver_cmd_tx: mpsc::Sender<DriverCommand>,
) {
for _ in 0..3 {
// A passenger has arrived..
driver_cmd_tx
.send(DriverCommand::PassengerArrived {
at: 0,
destination: 2,
})
.await
.unwrap();
// Wait until they are delivered..
while let Ok(evt) = events_rx.recv().await {
if let BuildingEvent::PassengerDelivered(_) = evt {
break;
}
}
}
driver_cmd_tx.send(DriverCommand::Halt).await.unwrap();
}

View File

@ -0,0 +1,23 @@
use building::BuildingEvent;
use tokio::sync::broadcast;
mod building;
mod controller;
mod driver;
#[tokio::main]
async fn main() {
let building = driver::make_building();
let (building_task, events_rx, building_cmd_tx, driver_cmd_tx) = building.start();
tokio::spawn(print_events(events_rx.resubscribe()));
tokio::spawn(driver::driver(events_rx.resubscribe(), driver_cmd_tx));
tokio::spawn(controller::controller(events_rx, building_cmd_tx));
building_task.await.unwrap();
}
async fn print_events(mut events_rx: broadcast::Receiver<BuildingEvent>) {
while let Ok(evt) = events_rx.recv().await {
println!("BuildingEvent::{:?}", evt);
}
}

View File

@ -0,0 +1,88 @@
# Multi-threaded Link Checker
Let us use our new knowledge to create a multi-threaded link checker. It should
start at a webpage and check that links on the page are valid. It should
recursively check other pages on the same domain and keep doing this until all
pages have been validated.
For this, you will need an HTTP client such as [`reqwest`][1]. Create a new
Cargo project and `reqwest` it as a dependency with:
```shell
$ cargo new link-checker
$ cd link-checker
$ cargo add --features blocking,rustls-tls reqwest
```
> If `cargo add` fails with `error: no such subcommand`, then please edit the
> `Cargo.toml` file by hand. Add the dependencies listed below.
You will also need a way to find links. We can use [`scraper`][2] for that:
```shell
$ cargo add scraper
```
Finally, we'll need some way of handling errors. We use [`thiserror`][3] for
that:
```shell
$ cargo add thiserror
```
The `cargo add` calls will update the `Cargo.toml` file to look like this:
<!-- File Cargo.toml -->
```toml
[package]
name = "link-checker"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
reqwest = { version = "0.11.12", features = ["blocking", "rustls-tls"] }
scraper = "0.13.0"
thiserror = "1.0.37"
```
You can now download the start page. Try with a small site such as
`https://www.google.org/`.
Your `src/main.rs` file should look something like this:
<!-- File src/main.rs -->
```rust,compile_fail
{{#include link-checker.rs:setup}}
{{#include link-checker.rs:extract_links}}
fn main() {
let start_url = Url::parse("https://www.google.org").unwrap();
let response = get(start_url).unwrap();
match extract_links(response) {
Ok(links) => println!("Links: {links:#?}"),
Err(err) => println!("Could not extract links: {err:#}"),
}
}
```
Run the code in `src/main.rs` with
```shell
$ cargo run
```
## Tasks
* Use threads to check the links in parallel: send the URLs to be checked to a
channel and let a few threads check the URLs in parallel.
* Extend this to recursively extract links from all pages on the
`www.google.org` domain. Put an upper limit of 100 pages or so so that you
don't end up being blocked by the site.
[1]: https://docs.rs/reqwest/
[2]: https://docs.rs/scraper/
[3]: https://docs.rs/thiserror/

View File

@ -0,0 +1,89 @@
// Copyright 2022 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: setup
use reqwest::blocking::{get, Response};
use reqwest::Url;
use scraper::{Html, Selector};
use thiserror::Error;
#[derive(Error, Debug)]
enum Error {
#[error("request error: {0}")]
ReqwestError(#[from] reqwest::Error),
}
// ANCHOR_END: setup
// ANCHOR: extract_links
fn extract_links(response: Response) -> Result<Vec<Url>, Error> {
let base_url = response.url().to_owned();
let document = response.text()?;
let html = Html::parse_document(&document);
let selector = Selector::parse("a").unwrap();
let mut valid_urls = Vec::new();
for element in html.select(&selector) {
if let Some(href) = element.value().attr("href") {
match base_url.join(href) {
Ok(url) => valid_urls.push(url),
Err(err) => {
println!("On {base_url}: could not parse {href:?}: {err} (ignored)",);
}
}
}
}
Ok(valid_urls)
}
// ANCHOR_END: extract_links
fn check_links(url: Url) -> Result<Vec<Url>, Error> {
println!("Checking {url}");
let response = get(url.to_owned())?;
if !response.status().is_success() {
return Ok(vec![url.to_owned()]);
}
let links = extract_links(response)?;
for link in &links {
println!("{link}, {:?}", link.domain());
}
let mut failed_links = Vec::new();
for link in links {
if link.domain() != url.domain() {
println!("Checking external link: {link}");
let response = get(link.clone())?;
if !response.status().is_success() {
println!("Error on {url}: {link} failed: {}", response.status());
failed_links.push(link);
}
} else {
println!("Checking link in same domain: {link}");
failed_links.extend(check_links(link)?)
}
}
Ok(failed_links)
}
fn main() {
let start_url = Url::parse("https://www.google.org").unwrap();
match check_links(start_url) {
Ok(links) => println!("Links: {links:#?}"),
Err(err) => println!("Could not extract links: {err:#}"),
}
}

View File

@ -0,0 +1,16 @@
# Exercises
Let us practice our new concurrency skills with
* Dining philosophers: a classic problem in concurrency.
* Multi-threaded link checker: a larger project where you'll use Cargo to
download dependencies and then check links in parallel.
<details>
After looking at the exercises, you can look at the [solutions] provided.
[solutions]: solutions-morning.md
</details>

View File

@ -0,0 +1,10 @@
# Concurrency Morning Exercise
## Dining Philosophers
([back to exercise](dining-philosophers.md))
```rust
{{#include dining-philosophers.rs}}
```