- Simulates a restaurant w/ tables
- Maintains a list of outstanding orders for each table
- Randomly assigns the time to cook (5-15 minutes) to each order
- Accepts below HTTP requests
-
Add
- Adds orders to a specified table
-
Remove
- Removes an order of a specified UUID
-
Query item
- Returns an order of a specified UUID
-
Query all items
- Returns outstanding orders of a specified table
-
rocket
requires nightly version of Rust
$ cd [Project root]
$ cargo run --bin application
$ cd [Project root]
$ docker-compose up --build
[Project root]/Rocket.toml
name | description |
---|---|
address | Address that the application listens to |
port | Port that the application listens to |
num_tables | # of tables in the restaurant |
max_table_items | Maximum # of outstanding orders that a table can have |
one_min_in_sec | # of seconds that constitutes 1 minute |
log | Rocket log level. Valid values are: "normal", "debug", or "critical" |
secret_key | Rocket secret_key that is a 256-bit base64 encoded string. Required for production |
Tag | Method | Endpoint | Parameters | Response | Description |
---|---|---|---|---|---|
Add | POST | /v1/table/[table_id]/items | item_names: string[] | 200: Item[], 429: TooManyItems (max item exceeded), 406: NotAcceptable (bad table id) | Adds items w/ specified names to the specified table and returns added items |
Remove | DELETE | /v1/table/[table_id]/item/[uuid] | 200: Ok, 404: NotFound, 406: NotAcceptable | Removes an item of the specified UUID | |
Query table | GET | /v1/table/[table_id]/items | 200: Item[], 406: NotAcceptable | Returns all items of the specified table that is being cooked | |
Query item | GET | /v1/table/[table_id]/item/[uuid] | 200: Item, 404: NotFound, 406: NotAcceptable | Returns an item of the specified UUID |
- 0 <=
table_id
< num_tables - Item object schema:
{ uuid: string, name: string, table_id: number, created_at: number, ready_at: number, is_removed: boolean, }
Rocket HTTP Server -> OrderMgr
- HTTP server directly forwards each request to a corresponding public method of
OrderMgr
OrderMgr
is in charge of maintaining the list of outstanding orders of each table
- Maintains outstanding orders of each table with
TableOrder
- Stores
TableOrder
s in aVector
TableOrder
maintains a priority queue of outstanding orders with the order with minimumready_at
at the rootTableOrder
also maintains a hash table of outstanding orders with orderUUID
as the key and theItem
(order) as the value- An Item is shared by the priority queue and hash table
- When a client request is made, Items whose
ready_at
is older than or equal to now is popped out of the priority queue and also removed from the hash table
Vector
+-----------+-----+
| (Table 0) | ... |
+-----------+-----+
|
V
TableOrder ---------------+ ..
| V /
+---> Hash table Priority queue [ ]
+-------+ / \
| UUID1 | ---> Item 1 <--- [ ] [ ]
+-------+ / \
| ... | .. ..
+-------+
- Looking for a table in a Vector is O(1)
- Adding an item to priority queue is O(log n) since priority queue is BinaryHeap
- Adding an item to hash table is O(1)
- Popping out an item is O(log n) due to using BinaryHeap
- Removing an item is O(1):
- Removing from hash table is O(1)
- An item is not removed from priority queue, but marked as removed. This is also O(1). The item will stay in priority queue until it's popped out
- Getting an item is O(1) since the item is obtained from hash table
- Getting all items is O(n) since it gets all values from hash table
-
RefCell
is used in multi threaded context and the lock is done atTableOrders
level for all operations because of that.-
Each
Item
object needs to be owned byBinaryHeap
andHashMap
inTableOrders
. A wrapper type that does reference counting GC is needed. -
Since
TableOrders
is used byOrderMgr
andOrderMgr
runs on multiple threads, the reference counting wrapper needs to beArc
instead ofRc
. -
Arc
doesn't expose mutable reference of the contained type, butItem
needs to be modified upon deletion. ThereforeArc
needed to be wrapped by something that exposes mutable reference. -
RefCell
andMutex
exposes mutable refernce.RefCell
is designed for single-threaded environment andMutex
is designed for multi-threaded environment. -
Since
Item
is used in multi-threaded environment,Mutex
should be used. ButMutex
cannnot be used sinceBinaryHeap
requiresOrd
to the contained type, butMutex
doesn't implementOrd
. So,RefCell
needs to be used. -
To use
RefCell
,Sync
andSend
unsafe traits are implemented toTableOrders
.TableOrders
is locked for for all operations including queries although read lock should suffice for queries.
-
-
To unwrap
Arc<RefCell<Item>>
toItem
, newItem
is manually created.
- Changed to update the list of items being cooked not only by add and remove requests, but also with query item and query all items requests.
Since I'm new to async in Rust and it turned out that Reqwest blocking client doesn't work well in highly multi-threaded environment, 2 clients were prepared:
- Barely working Rust-based client written with minimum async knowledge
- Fully working Node-based client
- Both clients endlessly execute below sequence on client threads:
- Select a table
- Add 1 item to the table
- Get the added item from the table
- Get all items of the table
- Remove the added item from the table
- Runs 10 client threads
- The number of tables is hardcoded to 100
- On Linux,
reqwest
requires OpenSSL 1.0.1, 1.0.2, 1.1.0, or 1.1.1 with headersOPENSSL_LIB_DIR
andOPENSSL_INCLUDE_DIR
need to be exported
- Client requests often fail with
hyper::Error(IncompleteMessage): connection closed before message completed
. This seems to be HTTP client issue. - Unable to find a random number generator that works inside async. So table is selected based on client ID and the current timestamp.
- Unable to find a way to execute vector of spawned async function. Using
futures::future::join_all
makes all http request fail w/reqwest::Error { kind: Request, url: Url { scheme: "http", host: Some(Domain("localhost")), port: Some(8888), ..., query: None, fragment: None }, source: hyper::Error(Connect, ConnectError("dns error", Custom { kind: Interrupted, error: JoinError::Cancelled })) }
$ cd [Project root]
$ cargo run --bin client
- npm
- node.js
$ cd [Project root]/node-client
$ npm install
$ npm start
- All logs for the requests made by client threads
- Error and warning logs of the client thread requests