initial commit
This commit is contained in:
4
exercises/07_threads/00_intro/Cargo.toml
Normal file
4
exercises/07_threads/00_intro/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "intro_07"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
17
exercises/07_threads/00_intro/src/lib.rs
Normal file
17
exercises/07_threads/00_intro/src/lib.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
fn intro() -> &'static str {
|
||||
// TODO: fix me 👇
|
||||
"I'm ready to _!"
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::intro;
|
||||
|
||||
#[test]
|
||||
fn test_intro() {
|
||||
assert_eq!(
|
||||
intro(),
|
||||
"I'm ready to build a concurrent ticket management system!"
|
||||
);
|
||||
}
|
||||
}
|
||||
4
exercises/07_threads/01_threads/Cargo.toml
Normal file
4
exercises/07_threads/01_threads/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "threads"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
49
exercises/07_threads/01_threads/src/lib.rs
Normal file
49
exercises/07_threads/01_threads/src/lib.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
// TODO: implement a multi-threaded version of the `sum` function
|
||||
// using `spawn` and `join`.
|
||||
// Given a vector of integers, split the vector into two halves and
|
||||
// sum each half in a separate thread.
|
||||
|
||||
// Caveat: We can't test *how* the function is implemented,
|
||||
// we can only verify that it produces the correct result.
|
||||
// You _could_ pass this test by just returning `v.iter().sum()`,
|
||||
// but that would defeat the purpose of the exercise.
|
||||
//
|
||||
// Hint: you won't be able to get the spawned threads to _borrow_
|
||||
// slices of the vector directly. You'll need to allocate new
|
||||
// vectors for each half of the original vector. We'll see why
|
||||
// this is necessary in the next exercise.
|
||||
use std::thread;
|
||||
|
||||
pub fn sum(v: Vec<i32>) -> i32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(sum(vec![]), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one() {
|
||||
assert_eq!(sum(vec![1]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5]), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nine() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), 45);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 55);
|
||||
}
|
||||
}
|
||||
4
exercises/07_threads/02_static/Cargo.toml
Normal file
4
exercises/07_threads/02_static/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "static"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
43
exercises/07_threads/02_static/src/lib.rs
Normal file
43
exercises/07_threads/02_static/src/lib.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
// TODO: Given a static slice of integers, split the slice into two halves and
|
||||
// sum each half in a separate thread.
|
||||
// Do not allocate any additional memory!
|
||||
use std::thread;
|
||||
|
||||
pub fn sum(slice: &'static [i32]) -> i32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
static ARRAY: [i32; 0] = [];
|
||||
assert_eq!(sum(&ARRAY), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one() {
|
||||
static ARRAY: [i32; 1] = [1];
|
||||
assert_eq!(sum(&ARRAY), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five() {
|
||||
static ARRAY: [i32; 5] = [1, 2, 3, 4, 5];
|
||||
assert_eq!(sum(&ARRAY), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nine() {
|
||||
static ARRAY: [i32; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
assert_eq!(sum(&ARRAY), 45);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten() {
|
||||
static ARRAY: [i32; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
assert_eq!(sum(&ARRAY), 55);
|
||||
}
|
||||
}
|
||||
4
exercises/07_threads/03_leak/Cargo.toml
Normal file
4
exercises/07_threads/03_leak/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "leaking"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
40
exercises/07_threads/03_leak/src/lib.rs
Normal file
40
exercises/07_threads/03_leak/src/lib.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
// TODO: Given a vector of integers, leak its heap allocation.
|
||||
// Then split the resulting static slice into two halves and
|
||||
// sum each half in a separate thread.
|
||||
// Hint: check out `Vec::leak`.
|
||||
|
||||
use std::thread;
|
||||
|
||||
pub fn sum(v: Vec<i32>) -> i32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(sum(vec![]), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one() {
|
||||
assert_eq!(sum(vec![1]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5]), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nine() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), 45);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 55);
|
||||
}
|
||||
}
|
||||
4
exercises/07_threads/04_scoped_threads/Cargo.toml
Normal file
4
exercises/07_threads/04_scoped_threads/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "scoped_threads"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
37
exercises/07_threads/04_scoped_threads/src/lib.rs
Normal file
37
exercises/07_threads/04_scoped_threads/src/lib.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
// TODO: Given a vector of integers, split it in two halves
|
||||
// and compute the sum of each half in a separate thread.
|
||||
// Don't perform any heap allocation. Don't leak any memory.
|
||||
|
||||
pub fn sum(v: Vec<i32>) -> i32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(sum(vec![]), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one() {
|
||||
assert_eq!(sum(vec![1]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5]), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nine() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]), 45);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten() {
|
||||
assert_eq!(sum(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 55);
|
||||
}
|
||||
}
|
||||
7
exercises/07_threads/05_channels/Cargo.toml
Normal file
7
exercises/07_threads/05_channels/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "channels"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
23
exercises/07_threads/05_channels/src/data.rs
Normal file
23
exercises/07_threads/05_channels/src/data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
23
exercises/07_threads/05_channels/src/lib.rs
Normal file
23
exercises/07_threads/05_channels/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
|
||||
pub enum Command {
|
||||
Insert(todo!()),
|
||||
}
|
||||
|
||||
// Start the system by spawning the server thread.
|
||||
// It returns a `Sender` instance which can then be used
|
||||
// by one or more clients to interact with the server.
|
||||
pub fn launch() -> Sender<Command> {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || server(receiver));
|
||||
sender
|
||||
}
|
||||
|
||||
// TODO: The server task should **never** stop.
|
||||
// Enter a loop: wait for a command to show up in
|
||||
// the channel, then execute it, then start waiting
|
||||
// for the next command.
|
||||
pub fn server(receiver: Receiver<Command>) {}
|
||||
33
exercises/07_threads/05_channels/src/store.rs
Normal file
33
exercises/07_threads/05_channels/src/store.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Ticket>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
self.tickets.insert(id, ticket);
|
||||
id
|
||||
}
|
||||
}
|
||||
32
exercises/07_threads/05_channels/tests/insert.rs
Normal file
32
exercises/07_threads/05_channels/tests/insert.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
// TODO: Set `move_forward` to `true` in `ready` when you think you're done with this exercise.
|
||||
// Feel free to call an instructor to verify your solution!
|
||||
use channels::data::TicketDraft;
|
||||
use channels::{launch, Command};
|
||||
use std::time::Duration;
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
|
||||
#[test]
|
||||
fn a_thread_is_spawned() {
|
||||
let sender = launch();
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
|
||||
sender
|
||||
.send(Command::Insert(TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
}))
|
||||
// If the thread is no longer running, this will panic
|
||||
// because the channel will be closed.
|
||||
.expect("Did you actually spawn a thread? The channel is closed!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ready() {
|
||||
// There's very little that we can check automatically in this exercise,
|
||||
// since our server doesn't expose any **read** actions.
|
||||
// We have no way to know if the inserts are actually happening and if they
|
||||
// are happening correctly.
|
||||
let move_forward = false;
|
||||
|
||||
assert!(move_forward);
|
||||
}
|
||||
4
exercises/07_threads/06_interior_mutability/Cargo.toml
Normal file
4
exercises/07_threads/06_interior_mutability/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "interior_mutability"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
46
exercises/07_threads/06_interior_mutability/src/lib.rs
Normal file
46
exercises/07_threads/06_interior_mutability/src/lib.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
// TODO: Use `Rc` and `RefCell` to implement `DropTracker<T>`, a wrapper around a value of type `T`
|
||||
// that increments a shared `usize` counter every time the wrapped value is dropped.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct DropTracker<T> {
|
||||
value: T,
|
||||
counter: todo!(),
|
||||
}
|
||||
|
||||
impl<T> DropTracker<T> {
|
||||
pub fn new(value: T, counter: todo!()) -> Self {
|
||||
Self { value, counter }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for DropTracker<T> {
|
||||
fn drop(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let counter = Rc::new(RefCell::new(0));
|
||||
let _ = DropTracker::new((), Rc::clone(&counter));
|
||||
assert_eq!(*counter.borrow(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple() {
|
||||
let counter = Rc::new(RefCell::new(0));
|
||||
|
||||
{
|
||||
let a = DropTracker::new(5, Rc::clone(&counter));
|
||||
let b = DropTracker::new(6, Rc::clone(&counter));
|
||||
}
|
||||
|
||||
assert_eq!(*counter.borrow(), 2);
|
||||
}
|
||||
}
|
||||
7
exercises/07_threads/07_ack/Cargo.toml
Normal file
7
exercises/07_threads/07_ack/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "response"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
23
exercises/07_threads/07_ack/src/data.rs
Normal file
23
exercises/07_threads/07_ack/src/data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
39
exercises/07_threads/07_ack/src/lib.rs
Normal file
39
exercises/07_threads/07_ack/src/lib.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use crate::store::TicketStore;
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
|
||||
// Refer to the tests to understand the expected schema.
|
||||
pub enum Command {
|
||||
Insert { todo!() },
|
||||
Get { todo!() }
|
||||
}
|
||||
|
||||
pub fn launch() -> Sender<Command> {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || server(receiver));
|
||||
sender
|
||||
}
|
||||
|
||||
// TODO: handle incoming commands as expected.
|
||||
pub fn server(receiver: Receiver<Command>) {
|
||||
let mut store = TicketStore::new();
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(Command::Insert {}) => {
|
||||
todo!()
|
||||
}
|
||||
Ok(Command::Get {
|
||||
todo!()
|
||||
}) => {
|
||||
todo!()
|
||||
}
|
||||
Err(_) => {
|
||||
// There are no more senders, so we can safely break
|
||||
// and shut down the server.
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
37
exercises/07_threads/07_ack/src/store.rs
Normal file
37
exercises/07_threads/07_ack/src/store.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Ticket>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
self.tickets.insert(id, ticket);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
||||
self.tickets.get(&id)
|
||||
}
|
||||
}
|
||||
45
exercises/07_threads/07_ack/tests/insert.rs
Normal file
45
exercises/07_threads/07_ack/tests/insert.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use response::data::{Status, Ticket, TicketDraft};
|
||||
use response::store::TicketId;
|
||||
use response::{launch, Command};
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
|
||||
#[test]
|
||||
fn insert_works() {
|
||||
let sender = launch();
|
||||
let (response_sender, response_receiver) = std::sync::mpsc::channel();
|
||||
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
let command = Command::Insert {
|
||||
draft: draft.clone(),
|
||||
response_sender,
|
||||
};
|
||||
|
||||
sender
|
||||
.send(command)
|
||||
// If the thread is no longer running, this will panic
|
||||
// because the channel will be closed.
|
||||
.expect("Did you actually spawn a thread? The channel is closed!");
|
||||
|
||||
let ticket_id: TicketId = response_receiver.recv().expect("No response received!");
|
||||
|
||||
let (response_sender, response_receiver) = std::sync::mpsc::channel();
|
||||
let command = Command::Get {
|
||||
id: ticket_id,
|
||||
response_sender,
|
||||
};
|
||||
sender
|
||||
.send(command)
|
||||
.expect("Did you actually spawn a thread? The channel is closed!");
|
||||
|
||||
let ticket: Ticket = response_receiver
|
||||
.recv()
|
||||
.expect("No response received!")
|
||||
.unwrap();
|
||||
assert_eq!(ticket_id, ticket.id);
|
||||
assert_eq!(ticket.status, Status::ToDo);
|
||||
assert_eq!(ticket.title, draft.title);
|
||||
assert_eq!(ticket.description, draft.description);
|
||||
}
|
||||
7
exercises/07_threads/08_client/Cargo.toml
Normal file
7
exercises/07_threads/08_client/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
23
exercises/07_threads/08_client/src/data.rs
Normal file
23
exercises/07_threads/08_client/src/data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
66
exercises/07_threads/08_client/src/lib.rs
Normal file
66
exercises/07_threads/08_client/src/lib.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use crate::data::{Ticket, TicketDraft};
|
||||
use crate::store::{TicketId, TicketStore};
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
|
||||
#[derive(Clone)]
|
||||
// TODO: flesh out the client implementation.
|
||||
pub struct TicketStoreClient {}
|
||||
|
||||
impl TicketStoreClient {
|
||||
// Feel free to panic on all errors, for simplicity.
|
||||
pub fn insert(&self, draft: TicketDraft) -> TicketId {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Option<Ticket> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launch() -> TicketStoreClient {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || server(receiver));
|
||||
todo!()
|
||||
}
|
||||
|
||||
// No longer public! This becomes an internal detail of the library now.
|
||||
enum Command {
|
||||
Insert {
|
||||
draft: TicketDraft,
|
||||
response_channel: Sender<TicketId>,
|
||||
},
|
||||
Get {
|
||||
id: TicketId,
|
||||
response_channel: Sender<Option<Ticket>>,
|
||||
},
|
||||
}
|
||||
|
||||
fn server(receiver: Receiver<Command>) {
|
||||
let mut store = TicketStore::new();
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(Command::Insert {
|
||||
draft,
|
||||
response_channel,
|
||||
}) => {
|
||||
let id = store.add_ticket(draft);
|
||||
let _ = response_channel.send(id);
|
||||
}
|
||||
Ok(Command::Get {
|
||||
id,
|
||||
response_channel,
|
||||
}) => {
|
||||
let ticket = store.get(id);
|
||||
let _ = response_channel.send(ticket.cloned());
|
||||
}
|
||||
Err(_) => {
|
||||
// There are no more senders, so we can safely break
|
||||
// and shut down the server.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
exercises/07_threads/08_client/src/store.rs
Normal file
37
exercises/07_threads/08_client/src/store.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Ticket>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
self.tickets.insert(id, ticket);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
||||
self.tickets.get(&id)
|
||||
}
|
||||
}
|
||||
21
exercises/07_threads/08_client/tests/insert.rs
Normal file
21
exercises/07_threads/08_client/tests/insert.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use client::data::{Status, TicketDraft};
|
||||
use client::launch;
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
|
||||
#[test]
|
||||
fn insert_works() {
|
||||
// Notice how much simpler the test is now that we have a client to handle the details!
|
||||
let client = launch();
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
let ticket_id = client.insert(draft.clone());
|
||||
|
||||
let client2 = client.clone();
|
||||
let ticket = client2.get(ticket_id).unwrap();
|
||||
assert_eq!(ticket_id, ticket.id);
|
||||
assert_eq!(ticket.status, Status::ToDo);
|
||||
assert_eq!(ticket.title, draft.title);
|
||||
assert_eq!(ticket.description, draft.description);
|
||||
}
|
||||
7
exercises/07_threads/09_bounded/Cargo.toml
Normal file
7
exercises/07_threads/09_bounded/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "bounded"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
23
exercises/07_threads/09_bounded/src/data.rs
Normal file
23
exercises/07_threads/09_bounded/src/data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
66
exercises/07_threads/09_bounded/src/lib.rs
Normal file
66
exercises/07_threads/09_bounded/src/lib.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
// TODO: Convert the implementation to use bounded channels.
|
||||
use crate::data::{Ticket, TicketDraft};
|
||||
use crate::store::{TicketId, TicketStore};
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStoreClient {
|
||||
sender: todo!(),
|
||||
}
|
||||
|
||||
impl TicketStoreClient {
|
||||
pub fn insert(&self, draft: TicketDraft) -> Result<TicketId, todo!()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Result<Option<Ticket>, todo!()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launch(capacity: usize) -> TicketStoreClient {
|
||||
todo!();
|
||||
std::thread::spawn(move || server(receiver));
|
||||
todo!()
|
||||
}
|
||||
|
||||
enum Command {
|
||||
Insert {
|
||||
draft: TicketDraft,
|
||||
response_channel: todo!(),
|
||||
},
|
||||
Get {
|
||||
id: TicketId,
|
||||
response_channel: todo!(),
|
||||
},
|
||||
}
|
||||
|
||||
pub fn server(receiver: Receiver<Command>) {
|
||||
let mut store = TicketStore::new();
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(Command::Insert {
|
||||
draft,
|
||||
response_channel,
|
||||
}) => {
|
||||
let id = store.add_ticket(draft);
|
||||
todo!()
|
||||
}
|
||||
Ok(Command::Get {
|
||||
id,
|
||||
response_channel,
|
||||
}) => {
|
||||
let ticket = store.get(id);
|
||||
todo!()
|
||||
}
|
||||
Err(_) => {
|
||||
// There are no more senders, so we can safely break
|
||||
// and shut down the server.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
exercises/07_threads/09_bounded/src/store.rs
Normal file
37
exercises/07_threads/09_bounded/src/store.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Ticket>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
self.tickets.insert(id, ticket);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
||||
self.tickets.get(&id)
|
||||
}
|
||||
}
|
||||
20
exercises/07_threads/09_bounded/tests/insert.rs
Normal file
20
exercises/07_threads/09_bounded/tests/insert.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use bounded::data::{Status, TicketDraft};
|
||||
use bounded::launch;
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
|
||||
#[test]
|
||||
fn works() {
|
||||
let client = launch(5);
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
let ticket_id = client.insert(draft.clone()).unwrap();
|
||||
|
||||
let client2 = client.clone();
|
||||
let ticket = client2.get(ticket_id).unwrap().unwrap();
|
||||
assert_eq!(ticket_id, ticket.id);
|
||||
assert_eq!(ticket.status, Status::ToDo);
|
||||
assert_eq!(ticket.title, draft.title);
|
||||
assert_eq!(ticket.description, draft.description);
|
||||
}
|
||||
8
exercises/07_threads/10_patch/Cargo.toml
Normal file
8
exercises/07_threads/10_patch/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "patch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.59"
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
31
exercises/07_threads/10_patch/src/data.rs
Normal file
31
exercises/07_threads/10_patch/src/data.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketPatch {
|
||||
pub id: TicketId,
|
||||
pub title: Option<TicketTitle>,
|
||||
pub description: Option<TicketDescription>,
|
||||
pub status: Option<Status>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
97
exercises/07_threads/10_patch/src/lib.rs
Normal file
97
exercises/07_threads/10_patch/src/lib.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||
|
||||
// TODO: Implement the patching functionality.
|
||||
use crate::data::{Ticket, TicketDraft, TicketPatch};
|
||||
use crate::store::{TicketId, TicketStore};
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStoreClient {
|
||||
sender: SyncSender<Command>,
|
||||
}
|
||||
|
||||
impl TicketStoreClient {
|
||||
pub fn insert(&self, draft: TicketDraft) -> Result<TicketId, OverloadedError> {
|
||||
let (response_sender, response_receiver) = sync_channel(1);
|
||||
self.sender
|
||||
.try_send(Command::Insert {
|
||||
draft,
|
||||
response_channel: response_sender,
|
||||
})
|
||||
.map_err(|_| OverloadedError)?;
|
||||
Ok(response_receiver.recv().unwrap())
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Result<Option<Ticket>, OverloadedError> {
|
||||
let (response_sender, response_receiver) = sync_channel(1);
|
||||
self.sender
|
||||
.try_send(Command::Get {
|
||||
id,
|
||||
response_channel: response_sender,
|
||||
})
|
||||
.map_err(|_| OverloadedError)?;
|
||||
Ok(response_receiver.recv().unwrap())
|
||||
}
|
||||
|
||||
pub fn update(&self, ticket_patch: TicketPatch) -> Result<(), OverloadedError> {}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("The store is overloaded")]
|
||||
pub struct OverloadedError;
|
||||
|
||||
pub fn launch(capacity: usize) -> TicketStoreClient {
|
||||
let (sender, receiver) = sync_channel(capacity);
|
||||
std::thread::spawn(move || server(receiver));
|
||||
TicketStoreClient { sender }
|
||||
}
|
||||
|
||||
enum Command {
|
||||
Insert {
|
||||
draft: TicketDraft,
|
||||
response_channel: SyncSender<TicketId>,
|
||||
},
|
||||
Get {
|
||||
id: TicketId,
|
||||
response_channel: SyncSender<Option<Ticket>>,
|
||||
},
|
||||
Update {
|
||||
patch: TicketPatch,
|
||||
response_channel: SyncSender<()>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn server(receiver: Receiver<Command>) {
|
||||
let mut store = TicketStore::new();
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(Command::Insert {
|
||||
draft,
|
||||
response_channel,
|
||||
}) => {
|
||||
let id = store.add_ticket(draft);
|
||||
let _ = response_channel.send(id);
|
||||
}
|
||||
Ok(Command::Get {
|
||||
id,
|
||||
response_channel,
|
||||
}) => {
|
||||
let ticket = store.get(id);
|
||||
let _ = response_channel.send(ticket.cloned());
|
||||
}
|
||||
Ok(Command::Update {
|
||||
patch,
|
||||
response_channel,
|
||||
}) => {
|
||||
todo!()
|
||||
}
|
||||
Err(_) => {
|
||||
// There are no more senders, so we can safely break
|
||||
// and shut down the server.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
exercises/07_threads/10_patch/src/store.rs
Normal file
41
exercises/07_threads/10_patch/src/store.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Ticket>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
self.tickets.insert(id, ticket);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Option<&Ticket> {
|
||||
self.tickets.get(&id)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: TicketId) -> Option<&mut Ticket> {
|
||||
self.tickets.get_mut(&id)
|
||||
}
|
||||
}
|
||||
31
exercises/07_threads/10_patch/tests/check.rs
Normal file
31
exercises/07_threads/10_patch/tests/check.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use patch::data::{Status, TicketDraft, TicketPatch};
|
||||
use patch::launch;
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
|
||||
#[test]
|
||||
fn works() {
|
||||
let client = launch(5);
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
let ticket_id = client.insert(draft.clone()).unwrap();
|
||||
|
||||
let ticket = client.get(ticket_id).unwrap().unwrap();
|
||||
assert_eq!(ticket_id, ticket.id);
|
||||
assert_eq!(ticket.status, Status::ToDo);
|
||||
assert_eq!(ticket.title, draft.title);
|
||||
assert_eq!(ticket.description, draft.description);
|
||||
|
||||
let patch = TicketPatch {
|
||||
id: ticket_id,
|
||||
title: None,
|
||||
description: None,
|
||||
status: Some(Status::InProgress),
|
||||
};
|
||||
client.update(patch).unwrap();
|
||||
|
||||
let ticket = client.get(ticket_id).unwrap().unwrap();
|
||||
assert_eq!(ticket.id, ticket_id);
|
||||
assert_eq!(ticket.status, Status::InProgress);
|
||||
}
|
||||
8
exercises/07_threads/11_locks/Cargo.toml
Normal file
8
exercises/07_threads/11_locks/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "locks"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.60"
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
23
exercises/07_threads/11_locks/src/data.rs
Normal file
23
exercises/07_threads/11_locks/src/data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
88
exercises/07_threads/11_locks/src/lib.rs
Normal file
88
exercises/07_threads/11_locks/src/lib.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
// TODO: Fill in the missing methods for `TicketStore`.
|
||||
// Notice how we no longer need a separate update command: `Get` now returns a handle to the ticket
|
||||
// which allows the caller to both modify and read the ticket.
|
||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::data::{Ticket, TicketDraft};
|
||||
use crate::store::{TicketId, TicketStore};
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStoreClient {
|
||||
sender: SyncSender<Command>,
|
||||
}
|
||||
|
||||
impl TicketStoreClient {
|
||||
pub fn insert(&self, draft: TicketDraft) -> Result<TicketId, OverloadedError> {
|
||||
let (response_sender, response_receiver) = sync_channel(1);
|
||||
self.sender
|
||||
.try_send(Command::Insert {
|
||||
draft,
|
||||
response_channel: response_sender,
|
||||
})
|
||||
.map_err(|_| OverloadedError)?;
|
||||
Ok(response_receiver.recv().unwrap())
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Result<Option<Arc<Mutex<Ticket>>>, OverloadedError> {
|
||||
let (response_sender, response_receiver) = sync_channel(1);
|
||||
self.sender
|
||||
.try_send(Command::Get {
|
||||
id,
|
||||
response_channel: response_sender,
|
||||
})
|
||||
.map_err(|_| OverloadedError)?;
|
||||
Ok(response_receiver.recv().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("The store is overloaded")]
|
||||
pub struct OverloadedError;
|
||||
|
||||
pub fn launch(capacity: usize) -> TicketStoreClient {
|
||||
let (sender, receiver) = sync_channel(capacity);
|
||||
std::thread::spawn(move || server(receiver));
|
||||
TicketStoreClient { sender }
|
||||
}
|
||||
|
||||
enum Command {
|
||||
Insert {
|
||||
draft: TicketDraft,
|
||||
response_channel: SyncSender<TicketId>,
|
||||
},
|
||||
Get {
|
||||
id: TicketId,
|
||||
response_channel: SyncSender<Option<Arc<Mutex<Ticket>>>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn server(receiver: Receiver<Command>) {
|
||||
let mut store = TicketStore::new();
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(Command::Insert {
|
||||
draft,
|
||||
response_channel,
|
||||
}) => {
|
||||
let id = store.add_ticket(draft);
|
||||
let _ = response_channel.send(id);
|
||||
}
|
||||
Ok(Command::Get {
|
||||
id,
|
||||
response_channel,
|
||||
}) => {
|
||||
let ticket = store.get(id);
|
||||
let _ = response_channel.send(ticket);
|
||||
}
|
||||
Err(_) => {
|
||||
// There are no more senders, so we can safely break
|
||||
// and shut down the server.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
exercises/07_threads/11_locks/src/store.rs
Normal file
40
exercises/07_threads/11_locks/src/store.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Arc<Mutex<Ticket>>>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
todo!();
|
||||
id
|
||||
}
|
||||
|
||||
// The `get` method should return a handle to the ticket
|
||||
// which allows the caller to either read or modify the ticket.
|
||||
pub fn get(&self, id: TicketId) -> Option<todo!()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
31
exercises/07_threads/11_locks/tests/check.rs
Normal file
31
exercises/07_threads/11_locks/tests/check.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use locks::data::{Status, TicketDraft};
|
||||
use locks::launch;
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
|
||||
#[test]
|
||||
fn works() {
|
||||
let client = launch(5);
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
let ticket_id = client.insert(draft.clone()).unwrap();
|
||||
|
||||
let ticket = client.get(ticket_id).unwrap().unwrap();
|
||||
{
|
||||
let mut ticket = ticket.lock().unwrap();
|
||||
assert_eq!(ticket_id, ticket.id);
|
||||
assert_eq!(ticket.status, Status::ToDo);
|
||||
assert_eq!(ticket.title, draft.title);
|
||||
assert_eq!(ticket.description, draft.description);
|
||||
|
||||
ticket.status = Status::InProgress;
|
||||
}
|
||||
|
||||
let ticket = client.get(ticket_id).unwrap().unwrap();
|
||||
{
|
||||
let ticket = ticket.lock().unwrap();
|
||||
assert_eq!(ticket_id, ticket.id);
|
||||
assert_eq!(ticket.status, Status::InProgress);
|
||||
}
|
||||
}
|
||||
8
exercises/07_threads/12_rw_lock/Cargo.toml
Normal file
8
exercises/07_threads/12_rw_lock/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "rwlock"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.60"
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
23
exercises/07_threads/12_rw_lock/src/data.rs
Normal file
23
exercises/07_threads/12_rw_lock/src/data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
87
exercises/07_threads/12_rw_lock/src/lib.rs
Normal file
87
exercises/07_threads/12_rw_lock/src/lib.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
// TODO: Replace `Mutex` with `RwLock` in the `TicketStore` struct and
|
||||
// all other relevant places to allow multiple readers to access the ticket store concurrently.
|
||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::data::{Ticket, TicketDraft};
|
||||
use crate::store::{TicketId, TicketStore};
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStoreClient {
|
||||
sender: SyncSender<Command>,
|
||||
}
|
||||
|
||||
impl TicketStoreClient {
|
||||
pub fn insert(&self, draft: TicketDraft) -> Result<TicketId, OverloadedError> {
|
||||
let (response_sender, response_receiver) = sync_channel(1);
|
||||
self.sender
|
||||
.try_send(Command::Insert {
|
||||
draft,
|
||||
response_channel: response_sender,
|
||||
})
|
||||
.map_err(|_| OverloadedError)?;
|
||||
Ok(response_receiver.recv().unwrap())
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Result<Option<Arc<Mutex<Ticket>>>, OverloadedError> {
|
||||
let (response_sender, response_receiver) = sync_channel(1);
|
||||
self.sender
|
||||
.try_send(Command::Get {
|
||||
id,
|
||||
response_channel: response_sender,
|
||||
})
|
||||
.map_err(|_| OverloadedError)?;
|
||||
Ok(response_receiver.recv().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("The store is overloaded")]
|
||||
pub struct OverloadedError;
|
||||
|
||||
pub fn launch(capacity: usize) -> TicketStoreClient {
|
||||
let (sender, receiver) = sync_channel(capacity);
|
||||
std::thread::spawn(move || server(receiver));
|
||||
TicketStoreClient { sender }
|
||||
}
|
||||
|
||||
enum Command {
|
||||
Insert {
|
||||
draft: TicketDraft,
|
||||
response_channel: SyncSender<TicketId>,
|
||||
},
|
||||
Get {
|
||||
id: TicketId,
|
||||
response_channel: SyncSender<Option<Arc<Mutex<Ticket>>>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn server(receiver: Receiver<Command>) {
|
||||
let mut store = TicketStore::new();
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(Command::Insert {
|
||||
draft,
|
||||
response_channel,
|
||||
}) => {
|
||||
let id = store.add_ticket(draft);
|
||||
let _ = response_channel.send(id);
|
||||
}
|
||||
Ok(Command::Get {
|
||||
id,
|
||||
response_channel,
|
||||
}) => {
|
||||
let ticket = store.get(id);
|
||||
let _ = response_channel.send(ticket);
|
||||
}
|
||||
Err(_) => {
|
||||
// There are no more senders, so we can safely break
|
||||
// and shut down the server.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
exercises/07_threads/12_rw_lock/src/store.rs
Normal file
41
exercises/07_threads/12_rw_lock/src/store.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Arc<Mutex<Ticket>>>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
let ticket = Arc::new(Mutex::new(ticket));
|
||||
self.tickets.insert(id, ticket);
|
||||
id
|
||||
}
|
||||
|
||||
// The `get` method should return a handle to the ticket
|
||||
// which allows the caller to either read or modify the ticket.
|
||||
pub fn get(&self, id: TicketId) -> Option<Arc<Mutex<Ticket>>> {
|
||||
self.tickets.get(&id).cloned()
|
||||
}
|
||||
}
|
||||
31
exercises/07_threads/12_rw_lock/tests/check.rs
Normal file
31
exercises/07_threads/12_rw_lock/tests/check.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use rwlock::data::{Status, TicketDraft};
|
||||
use rwlock::launch;
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
|
||||
#[test]
|
||||
fn works() {
|
||||
let client = launch(5);
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
let ticket_id = client.insert(draft.clone()).unwrap();
|
||||
|
||||
let ticket = client.get(ticket_id).unwrap().unwrap();
|
||||
let lock1 = ticket.read().unwrap();
|
||||
{
|
||||
let ticket = ticket.read().unwrap();
|
||||
assert_eq!(ticket_id, ticket.id);
|
||||
assert_eq!(ticket.status, Status::ToDo);
|
||||
assert_eq!(ticket.title, draft.title);
|
||||
assert_eq!(ticket.description, draft.description);
|
||||
}
|
||||
|
||||
drop(lock1);
|
||||
|
||||
let ticket = client.get(ticket_id).unwrap().unwrap();
|
||||
{
|
||||
let mut ticket = ticket.write().unwrap();
|
||||
ticket.status = Status::InProgress;
|
||||
}
|
||||
}
|
||||
7
exercises/07_threads/13_without_channels/Cargo.toml
Normal file
7
exercises/07_threads/13_without_channels/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "without_channels"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ticket_fields = { path = "../../../helpers/ticket_fields" }
|
||||
23
exercises/07_threads/13_without_channels/src/data.rs
Normal file
23
exercises/07_threads/13_without_channels/src/data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::store::TicketId;
|
||||
use ticket_fields::{TicketDescription, TicketTitle};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ticket {
|
||||
pub id: TicketId,
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TicketDraft {
|
||||
pub title: TicketTitle,
|
||||
pub description: TicketDescription,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
ToDo,
|
||||
InProgress,
|
||||
Done,
|
||||
}
|
||||
7
exercises/07_threads/13_without_channels/src/lib.rs
Normal file
7
exercises/07_threads/13_without_channels/src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// TODO: You don't actually have to change anything in the library itself!
|
||||
// We mostly had to **remove** code (the client type, the launch function, the command enum)
|
||||
// that's no longer necessary.
|
||||
// Fix the `todo!()` in the testing code and see how the new design can be used.
|
||||
|
||||
pub mod data;
|
||||
pub mod store;
|
||||
40
exercises/07_threads/13_without_channels/src/store.rs
Normal file
40
exercises/07_threads/13_without_channels/src/store.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::data::{Status, Ticket, TicketDraft};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TicketId(u64);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TicketStore {
|
||||
tickets: BTreeMap<TicketId, Arc<RwLock<Ticket>>>,
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl TicketStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tickets: BTreeMap::new(),
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_ticket(&mut self, ticket: TicketDraft) -> TicketId {
|
||||
let id = TicketId(self.counter);
|
||||
self.counter += 1;
|
||||
let ticket = Ticket {
|
||||
id,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
status: Status::ToDo,
|
||||
};
|
||||
let ticket = Arc::new(RwLock::new(ticket));
|
||||
self.tickets.insert(id, ticket);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: TicketId) -> Option<Arc<RwLock<Ticket>>> {
|
||||
self.tickets.get(&id).cloned()
|
||||
}
|
||||
}
|
||||
40
exercises/07_threads/13_without_channels/tests/check.rs
Normal file
40
exercises/07_threads/13_without_channels/tests/check.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::spawn;
|
||||
|
||||
use ticket_fields::test_helpers::{ticket_description, ticket_title};
|
||||
use without_channels::data::TicketDraft;
|
||||
use without_channels::store::TicketStore;
|
||||
|
||||
#[test]
|
||||
fn works() {
|
||||
let store = todo!();
|
||||
|
||||
let store1 = store.clone();
|
||||
let client1 = spawn(move || {
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
store1.write().unwrap().add_ticket(draft)
|
||||
});
|
||||
|
||||
let store2 = store.clone();
|
||||
let client2 = spawn(move || {
|
||||
let draft = TicketDraft {
|
||||
title: ticket_title(),
|
||||
description: ticket_description(),
|
||||
};
|
||||
store2.write().unwrap().add_ticket(draft)
|
||||
});
|
||||
|
||||
let ticket_id1 = client1.join().unwrap();
|
||||
let ticket_id2 = client2.join().unwrap();
|
||||
|
||||
let reader = store.read().unwrap();
|
||||
|
||||
let ticket1 = reader.get(ticket_id1).unwrap();
|
||||
assert_eq!(ticket_id1, ticket1.read().unwrap().id);
|
||||
|
||||
let ticket2 = reader.get(ticket_id2).unwrap();
|
||||
assert_eq!(ticket_id2, ticket2.read().unwrap().id);
|
||||
}
|
||||
4
exercises/07_threads/14_sync/Cargo.toml
Normal file
4
exercises/07_threads/14_sync/Cargo.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "sync"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
14
exercises/07_threads/14_sync/src/lib.rs
Normal file
14
exercises/07_threads/14_sync/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Not much to be exercised on `Sync`, just a thing to remember.
|
||||
fn outro() -> &'static str {
|
||||
"I have a good understanding of __!"
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::outro;
|
||||
|
||||
#[test]
|
||||
fn test_outro() {
|
||||
assert_eq!(outro(), "I have a good understanding of Send and Sync!");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user