initial commit

This commit is contained in:
2025-02-19 10:34:15 +05:30
commit b9cb4c290a
355 changed files with 18626 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
[package]
name = "intro_07"
version = "0.1.0"
edition = "2021"

View 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!"
);
}
}

View File

@@ -0,0 +1,4 @@
[package]
name = "threads"
version = "0.1.0"
edition = "2021"

View 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);
}
}

View File

@@ -0,0 +1,4 @@
[package]
name = "static"
version = "0.1.0"
edition = "2021"

View 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);
}
}

View File

@@ -0,0 +1,4 @@
[package]
name = "leaking"
version = "0.1.0"
edition = "2021"

View 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);
}
}

View File

@@ -0,0 +1,4 @@
[package]
name = "scoped_threads"
version = "0.1.0"
edition = "2021"

View 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);
}
}

View File

@@ -0,0 +1,7 @@
[package]
name = "channels"
version = "0.1.0"
edition = "2021"
[dependencies]
ticket_fields = { path = "../../../helpers/ticket_fields" }

View 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,
}

View 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>) {}

View 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
}
}

View 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);
}

View File

@@ -0,0 +1,4 @@
[package]
name = "interior_mutability"
version = "0.1.0"
edition = "2021"

View 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);
}
}

View File

@@ -0,0 +1,7 @@
[package]
name = "response"
version = "0.1.0"
edition = "2021"
[dependencies]
ticket_fields = { path = "../../../helpers/ticket_fields" }

View 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,
}

View 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
},
}
}
}

View 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)
}
}

View 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);
}

View File

@@ -0,0 +1,7 @@
[package]
name = "client"
version = "0.1.0"
edition = "2021"
[dependencies]
ticket_fields = { path = "../../../helpers/ticket_fields" }

View 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,
}

View 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;
}
}
}
}

View 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)
}
}

View 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);
}

View File

@@ -0,0 +1,7 @@
[package]
name = "bounded"
version = "0.1.0"
edition = "2021"
[dependencies]
ticket_fields = { path = "../../../helpers/ticket_fields" }

View 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,
}

View 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;
}
}
}
}

View 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)
}
}

View 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);
}

View 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" }

View 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,
}

View 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;
}
}
}
}

View 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)
}
}

View 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);
}

View 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" }

View 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,
}

View 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;
}
}
}
}

View 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!()
}
}

View 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);
}
}

View 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" }

View 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,
}

View 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;
}
}
}
}

View 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()
}
}

View 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;
}
}

View File

@@ -0,0 +1,7 @@
[package]
name = "without_channels"
version = "0.1.0"
edition = "2021"
[dependencies]
ticket_fields = { path = "../../../helpers/ticket_fields" }

View 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,
}

View 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;

View 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()
}
}

View 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);
}

View File

@@ -0,0 +1,4 @@
[package]
name = "sync"
version = "0.1.0"
edition = "2021"

View 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!");
}
}