lab/lab2/mod.rs
1//! Welcome to Lab 2! The goal of this lab is to use the RPC service we built in
2//! Lab 1 as the basis to impelement a scalable Tribbler infrastructure
3//!
4//! ## Lab 2
5//!
6//! Welcome to Lab 2. The goal of this lab is to use the RPC service we built in
7//! Lab 1 as the basis to impelement a scalable Tribbler infrastructure, split
8//! into separate front-ends and back-ends by implementing the [new_bin_client],
9//! [serve_keeper], and [new_front] functions
10//!
11//! In particular, you will do this by first wrapping the key-value server into
12//! a distributed key-value service called *bin storage*. Your implementation
13//! should fit the [tribbler::storage::BinStorage] trait. Then, on top of this
14//! storage layer, you will implement a stateless Tribbler front-end that fits
15//! the [tribbler::trib::Server] trait.
16//!
17//! ## Get Your Repo
18//!
19//! As mentioned earlier, we will send out an invitation link on Piazza/Canvas.
20//! Once available, access it and follow the instructions provided in the
21//! repository to get started. Each lab comes with its own starter code, so you
22//! will need to copy your solution for Lab 1 into Lab 2 repository, and start
23//! working from there.
24//!
25//! ## System Architecture
26//!
27//! The overall system architecture looks like this:
28//!
29//! 
30//!
31//! Our system is split into two parts. The upper part is the Tribbler service.
32//! The lower part is a general purpose distributed key-value store called "Bin
33//! Storage". Users' web browsers make AJAX calls to the Tribbler front-ends.
34//! These front-end servers then convert those requests into one or more calls
35//! to the bin storage client interface. This interface then uses RPC to talk to
36//! one or more of the simple key-value stores that we built in Lab 1.
37//!
38//! Separately, there is also a `keeper` running in the background that
39//! maintains the bin storage in a coherent state. Both the bin storage clients
40//! and the keeper use the same RPC interface (the one developed in Lab 1) to
41//! communicate with the back-ends. The interface semantics defined in
42//! [tribbler::storage::Storage] should be sufficient to perform all the bin
43//! storage tasks without any changes.
44//!
45//! ## Bin Storage
46//!
47//! **Bin Storage** is a distributed key-value system that combines a bunch of
48//! [tribbler::storage::Storage] back-ends and presents itself as a unified and
49//! scalable storage service over the network. Bin storage service is provided
50//! via the [tribbler::storage::BinStorage] trait.
51//!
52//! Conceptually, bin storage provides an infinite set of separate
53//! [Storage](tribbler::storage::Storage) instances called *bins*. Each bin has
54//! a different name, and a caller can fetch a bin via the
55//! [BinStorage::bin()](tribbler::storage::BinStorage) function. This is the
56//! only function in the [BinStorage](tribbler::storage::BinStorage) trait.
57//! Initially, all bins are empty.
58//!
59//! The bin storage service provides this service on top of a finite set of
60//! back-end key-value stores (Lab 1). Each bin (a virtual KV store) will be
61//! mapped to one of the key-value back-ends. Thus, this is a many-to-one
62//! mapping, with multiple bins potentially sharing a single back-end. You will
63//! do this by splitting the key space with prefixes (or suffixes) that encode
64//! the bin names. As an example, the figure below shows a bin storage hashing 6
65//! bins into 3 back-ends, where each bin is indicated with a different prefix.
66//! When a caller calls `get("some-key")` on the bin `"alice"`, for example, the
67//! bin storage client translates this into a `get("alice::some-key")` RPC call
68//! on back-end 0.
69//!
70//! 
71//!
72//! In addition to key-value stores in bins, the bin storage also maintains a
73//! coarsely synchronized logical clock across *all* the bins, so items in
74//! different bins can be reasonably ordered relative to each other. Since the
75//! back-ends are "dumb", and do not talk to each other, this clock
76//! synchronization is a job for the background keeper process.
77//!
78//! Note that the clocks are NOT real-time synchronized (which would be too
79//! expensive for a distributed system), but only coarsely synchronized. In
80//! particular, if a `clock()` call is issued at least 3 seconds after another
81//! `clock()` call, no matter which bins they are issued in, bin storage always
82//! guarantees that the later call always returns a clock value no smaller than
83//! the earlier one's. Note that a bin storage with one single back-end will
84//! trivially satisfy this requirement. The keeper's job comes in when there are
85//! multiple back-ends to keep in sync.
86//!
87//! As mentioned, we already implemented the back-end for Lab 1, and the
88//! key-value store API will not change. Both the bin storage client and the
89//! keeper will communicate with the "dumb" back-ends via the RPC calls we
90//! implemented, and cooperatively they will present a coherent bin storage
91//! interface to upper layer applications. Since we'll be reusing the work from
92//! Lab 1, make sure you've implemented the back-end correctly!
93//!
94//! ## Tribbler
95//!
96//! Before we look into how the Tribbler service works, let's first define what
97//! a tribble is.
98//!
99//! A [Tribble](tribbler::trib::Trib) is a struct that has 4 fields:
100//!
101//! ```rust
102//! pub struct Trib {
103//! /// who posted this trib
104//! pub user: String,
105//! /// the content of the trib
106//! pub message: String,
107//! /// the physical timestamp when posted
108//! pub time: u64,
109//! /// the logical timestamp when posted
110//! pub clock: u64,
111//! }
112//! ```
113//!
114//! `time` is a real-world timestamp, read from the machine's time on the
115//! front-end immediately after `post()` is called on a
116//! [Server](tribbler::trib::Server). In order to sort tribbles in a globally
117//! consistent and *reasonable* order, however, we cannot sort the tribbles only
118//! by this timestamp, as different front-ends may have different clocks. For
119//! sorting, the Tribbler service uses a distributed logical `clock` in `u64`.
120//!
121//! When sorting many tribbles into a single timeline, you should sort by the
122//! fields following this priority:
123//!
124//! 1. `clock` The logical timestamp.
125//! 2. `time` The physical timestamp.
126//! 3. `user` The user id.
127//! 4. `message` The message content.
128//!
129//! We call this the *Tribble Order*.
130//!
131//! ## Tribbler Service Interface
132//!
133//! The Tribbler service logic is defined by the
134//! [Server](tribbler::trib::Server) interface (in `tribbler/src/trib.rs`). This
135//! is how the web user interface interacts with a Tribbler server.
136//!
137//! --------------
138//!
139//! ```rust
140//! async fn sign_up(&self, user: &str) -> TribResult<()>;
141//! ```
142//!
143//! Creates a new user. After a user is created, it will never disappear in the
144//! system.
145//!
146//! Returns error when the username is invalid or the user already exists. If
147//! there are concurrent sign-ups, both sign-ups may succeed.
148//!
149//! Valid usernames must be no longer than
150//! [MAX_USERNAME_LEN](tribbler::trib::MAX_USERNAME_LEN) characters but not
151//! empty, must start with a lower-case letter, and can only contain lower-case
152//! letters or numbers. There is a helper function called
153//! [is_valid_username](tribbler::trib::is_valid_username) which you can use to
154//! check if a username is valid.
155//!
156//! ---
157//!
158//! ```rust
159//! async fn list_users(&self) -> TribResult<Vec<String>>;
160//! ```
161//!
162//! Lists at least [MIN_LIST_USER](tribbler::trib::MIN_LIST_USER) different
163//! registered users. When there are less than [MIN_LIST_USER](tribbler::trib::MIN_LIST_USER) users that have ever signed
164//! up, list all of them. The returned usernames should be sorted in
165//! alphabetical order.
166//!
167//! This is just for showing some users on the front page; this is not for
168//! listing all the users that have ever signed up, as that would be too
169//! expensive in a large system.
170//!
171//! ---
172//!
173//! ```rust
174//! async fn post(&self, who: &str, post: &str, clock: u64) -> TribResult<()>;
175//! ```
176//!
177//! Posts a tribble. `clock` is the maximum clock value this user client has
178//! ever seen (from reading tribbles via `home()` or `tribs()`). It returns
179//! error when the user does not exist or the post is too long (longer than
180//! [MAX_TRIB_LEN](tribbler::trib::MAX_TRIB_LEN)`).
181//!
182//! ---
183//!
184//! ```rust
185//! async fn tribs(&self, user: &str) -> TribResult<Vec<Arc<Trib>>>;
186//! ```
187//!
188//! Lists the recent [MAX_TRIB_FETCH](tribbler::trib::MAX_TRIB_FETCH) tribbles
189//! that a user posted. Tribbles needs to be sorted in the Tribble Order, and
190//! also in the same order that the user posted the tribbles.
191//!
192//! ---
193//!
194//! ```rust
195//! async fn follow(&self, who: &str, whom: &str) -> TribResult<()>;
196//! async fn unfollow(&self, who: &str, whom: &str) -> TribResult<()>;
197//! async fn is_following(&self, who: &str, whom: &str) -> TribResult<bool>;
198//! async fn following(&self, who: &str) -> TribResult<Vec<String>>;
199//! ```
200//!
201//! These are functions to follow/unfollow, check following and listing all
202//! following users of a user. Users can never follow/unfollow themselves. When
203//! calling with `who` equal to `whom`, the functions must return an error. When
204//! the user does not exist, the functions return an error. A user's attempt to unfollow a user that they are not currently following should return an error. If a user performs a
205//! `follow()` on another user over multiple clients concurrently, only one
206//! `follow()` should succeed with no errors.
207//!
208//! A user can follow at most [MAX_FOLLOWING](tribbler::trib::MAX_FOLLOWING)
209//! users. When a user tries to follow more than
210//! [MAX_FOLLOWING](tribbler::trib::MAX_FOLLOWING) users, `follow()` should
211//! return an error.
212//!
213//! ---
214//!
215//! ```rust
216//! async fn home(&self, user: &str) -> TribResult<Vec<Arc<Trib>>>;
217//! ```
218//!
219//! List the recent [MAX_TRIB_FETCH](tribbler::trib::MAX_TRIB_FETCH) tribbles
220//! that are posted on the user's timeline in Tribble Order. A user's timeline is the set of tribbles that are posted by that user or any user they are following. In addition, the
221//! ordering should always satisfy the following conditions:
222//!
223//! 1. If tribble A is posted after tribble B, and they are both posted by the
224//! same user, A always shows after B.
225//! 2. If tribble A is posted at least 10 seconds after tribble B, even if they
226//! are posted by different users, A always shows after B.
227//! 3. If tribble A is posted after A's poster sees tribble B, A always
228//! shows after B.
229//!
230//! A is *posted after* B means A calls `post()` after B's `post()` has
231//! returned.
232//!
233//! The function returns error when the user does not exist.
234//!
235//! ---
236//!
237//! In addition to normal errors, these calls might also return IO errors if the
238//! implementation is unable to communicate with a remote service. Returning a
239//! `Ok()` type means that the call is successfully executed; returning an
240//! `Err()` error means that the call may or may not have successfully executed
241//! (depending on when/where the error occurred).
242//!
243//! Note that the [tribbler::ref_impl] module contains a reference
244//! implementation for [Server](tribbler::trib::Server) (which you played with
245//! briefly in the lab setup).
246//!
247//! ## Entry Functions
248//!
249//! Your job for Lab 2 is two-fold.
250//!
251//! 1. Implement the bin storage service by implementing the bin storage
252//! client logic (the [new_bin_client] function) and the keeper (the
253//! [serve_keeper] function).
254//! 2. Reimplement the Tribbler service by translating all the Tribbler
255//! service calls into bin storage service calls (the [new_front]
256//! function).
257//!
258//! You can find these entry functions in `lab/src/lab2/lab.rs`:
259//!
260//! ```rust
261//! pub async fn new_bin_client(backs: Vec<String>) -> TribResult<Box<dyn BinStorage>>
262//! ```
263//!
264//! This function is similar to [crate::lab1::new_client] but instead returns a
265//! type implementing the [tribbler::storage::BinStorage] trait.
266//! [BinStorage](tribbler::storage::BinStorage) has only one function called
267//! `bin()`, which takes a bin name and returns a
268//! [Storage](tribbler::storage::Storage). A bin storage provides another layer
269//! of mapping, where the caller will first gets a key-value store (the bin)
270//! with a name and then performs calls on the returned store. Different bin
271//! names should logically return completely separate key-value storage spaces,
272//! even though multiple bins might share a single physical back-end store.
273//!
274//! For your convenience, we have provided a small package called
275//! [tribbler::colon] which you can use for escaping all the colons in a string.
276//! The escaping rule is simple, all `'|'` characters are escaped into `"||"`,
277//! and all `':'` characters are escaped into `"|;"`. Please use this package
278//! wisely.
279//!
280//! ---
281//!
282//! ```rust
283//! pub async fn serve_keeper(kc: KeeperConfig) -> TribResult<()>
284//! ```
285//!
286//! This function is a blocking function, similar to [crate::lab1::serve_back].
287//! It will spawn a keeper instance that maintains the back-ends in a bin store
288//! in consistent state. For Lab 2, there will be only one keeper and the keeper
289//! won't have a lot to do. In Lab 3 we'll be adding fault tolerance. Then,
290//! there will be multiple keepers, and they will handle maintaining consistency
291//! as back-ends die and rejoin.
292//!
293//! (Note: A blocking function in this context means that it will never return
294//! under normal operation. If it does return, it will be because it experienced
295//! an error).
296//!
297//! The [tribbler::config::KeeperConfig] struct contains all the information
298//! needed on back-ends, and also some additional information about other
299//! keepers which you can ignore until Lab 3:
300//!
301//! - `backs: Vec<String>`: This is the complete list of addresses to back-ends
302//! that the keeper needs to maintain.
303//! - `addrs: Vec<String>`: This is the list of addresses for keepers. Lab 2
304//! will have only one keeper; Lab 3 will always have least three keepers, but
305//! for now you can ignore this field.
306//! - `this: usize`: The index of this keeper (in the `keepers` list). For Lab
307//! 2, this will always be zero.
308//! - `id: u128`: A non-zero incarnation identifier for this keeper, indicating
309//! when this keeper was created relative to other keepers. For Lab 2, you may
310//! ignore this field.
311//! - `ready: Option<Sender<bool>>`: A ready signal channel. It works similarly
312//! to the `ready` channel in [tribbler::config::BackConfig] from Lab
313//! 1. When a keeper sends `true` on this channel, the distributed bin storage
314//! should be ready to serve. Therefore, if you need to initialize the
315//! physical back-ends in some way, make sure you do it before you send a
316//! signal over `ready`. Don't forget to send `false` to `ready` if the
317//! initialization fails.
318//! - `shutdown: Option<Receiver<bool>>`: A shutdown signal channel. If a
319//! shutdown channel is provided, When any message is received on this
320//! channel, shut down the keeper. If no channel is given, then the keeper
321//! should serve indefinitely.
322//!
323//! Although an address for each keeper is given in the `addrs` field, your
324//! keeper does not necessarily need to listen on the address given. The keeper
325//! address serves more like a unique keeper identifier, and identifies that a
326//! keeper should be running on a given machine.
327//!
328//! You can add whatever functionality you want to the keeper, so long as it is
329//! limited to maintaining the integrity and consistency of the bin storage, and
330//! the setup is limited to the three [new_bin_client], [new_front] and
331//! [serve_keeper] calls. In particular, since the keeper is logically part of
332//! the bin storage service, it may understand how a bin storage client
333//! translates keys. It should NOT rely on anything specific to Tribbler.
334//!
335//! ---
336//!
337//! ```rust
338//! pub async fn new_front(bin_storage: Box<dyn BinStorage>) -> TribResult<Box<dyn Server>>
339//! ```
340//!
341//! This function takes a bin storage structure, and returns an implementation
342//! of [Server](tribbler::trib::Server). The returned instance then will serve
343//! as a service front-end that takes Tribbler service requests, and translates
344//! them into one or more calls to the back-end key-value bin storage. This
345//! front-end should be stateless, concurrency safe, and ready to be killed at
346//! any time. This means that at any time during its execution on any call, the
347//! back-end key-value store always needs to stay in a consistent state. Also,
348//! note that one front-end might be taking multiple concurrent requests from
349//! the Web, and there might be multiple front-ends talking to the same
350//! back-end, so make sure your system handles all the concurrency issues
351//! correctly.
352//!
353//! Note that your tribbler service should not rely on any special features that
354//! you added to your bin storage client. In particular, we should be able to
355//! swap out your [BinStorage](tribbler::storage::BinStorage) for our own, and
356//! your Tribbler front-end should be perfectly happy (and none the wiser). In
357//! particular, this means that you cannot rely on the bin storage keeper to
358//! perform Tribbler related tasks. A front-ends may spawn additional background
359//! tasks if additional work is required by your implementation.
360//!
361//! ## Putting the pieces together
362//!
363//! Your first step should be to write the bin storage service. Since we might
364//! have multiple parts running at the same time, potentially on different
365//! machines, we need a configuration file that specifies the serving addresses
366//! of the back-ends and the keeper for the distributed bin storage. By default,
367//! the system expects a file named `bins.config`.
368//!
369//! `bins.json` is saved in JSON format, marshalling a
370//! [Config](tribbler::config::Config). We have a utility program called
371//! `bins-mkcfg` that can generate a `bins.json` file automatically.
372//!
373//! Find a directory to use as your working directory, then run:
374//!
375//! ```
376//! $ cargo run --bin bins-mkcfg -- --backs 3
377//! ```
378//!
379//! This will generate a file called `bins.json` under the current directory.
380//! `--backs 3` means there will be in total three back-end servers. For
381//! `bins-mkcfg`, there can be at most 300 back-ends and 10 keepers. However,
382//! you are free to create your own `bins.json` file that has more back-ends and
383//! keepers. For now, we'll use 3 back-ends and 1 keeper (the default value for
384//! `--keeps`).
385//!
386//! With this configuration file generated, we can now launch the back-ends:
387//!
388//! ```
389//! $ cargo run --bin bins-back
390//! ```
391//!
392//! This will read and parse the `bins.json` file, and spawn all the back-ends
393//! that have serving address on this host. Since all the back-ends we generate
394//! here are on `localhost`, all three will be spawned with this command, each
395//! in its own threaded runtime. You should see three log lines showing that
396//! three back-ends started, but listening on different ports. Aside from
397//! reading its task from the configuration file, `bins-back` isn't much
398//! different from the `kv-server` program.
399//!
400//! After the back-ends are ready, we can now start the keeper.
401//!
402//! ```
403//! $ cargo run --bin bins-keep
404//! ```
405//!
406//! This process should print a message that shows that the bin storage is ready
407//! to serve.
408//!
409//! To play with this distributed bin storage, we can use `bins-client`:
410//!
411//! ```
412//! $ bins-client
413//! (now working on bin "")
414//! > bin a
415//! (now working on bin "a")
416//! > get a
417//! Ok(None)
418//! > set a b
419//! Ok(true)
420//! > get a
421//! Ok(Some("b"))
422//! > bin t
423//! (now working on bin "t")
424//! > get a
425//! Ok(None)
426//! > bin a
427//! (now working on bin "a")
428//! > get a
429//! Ok(Some("b"))
430//! ...
431//! ```
432//!
433//! This program reads the back-end addresses from `bins.json` and can switch
434//! between different bins with the `bin` command. Otherwise, it operates
435//! similarly to `kv-client` from the previous lab. The default bin is the bin
436//! named "".
437//!
438//! Once our bin storage is up and running, we can launch our Tribbler
439//! front-end:
440//!
441//! ```
442//! $ cargo run --bin trib-front -- -s lab
443//! ```
444//!
445//! You have used this utility before. The only new thing here is the `-s lab`
446//! flag, which tells it to read the `bins.json` file and use our lab
447//! implementation. This will start a stateless front-end (which you implemented
448//! in this lab) that connects to the back-ends specified in `bins.json`.
449//!
450//! Now you can open your browser, connect to the front-end machine and play
451//! with your own implementation.
452//!
453//! If you want to use some other configuration file, use the `--config` flag.
454//! It is supported in all the utilities above.
455//!
456//! Again, once you've completed this lab, your Tribbler implementation should
457//! be able to support multiple front-ends and multiple back-ends in a nice,
458//! scalable (but not fault-tolerant) way.
459//!
460//! ## Assumptions
461//!
462//! For this lab, there are a number of simplifying assumptions we'll be making.
463//! Some of these assumptions are reasonable. Many of the rest we'll remove in
464//! Lab 3:
465//!
466//! - No network communication errors will occur.
467//! - Once a back-end or a keeper starts, it will remain online forever.
468//! - The system will always start in the following order: the back-ends, the
469//! keeper, then all the front-ends.
470//! - The [Storage](tribbler::storage::Storage) used in the back-end will return
471//! every `clock()` call in less than 1 second.
472//! - In the [Storage](tribbler::storage::Storage) used in the back-end, all IO
473//! on a single process are serialized (and hence the interface provides
474//! sequential consistency). Each visit to a key (e.g. checking if a key
475//! exist, locating its corresponding value, iterating over a number of keys)
476//! will take less than 1 millisecond. Reads and writes of 1MB of data or less
477//! as a value (in a list or a string) will take less than 1 millisecond. Note
478//! that `keys()` and `list_keys()` might take a longer time to complete
479//! because it needs to scan over all the keys.
480//! - Although the Tribbler front-ends can be killed at any time, the killing
481//! won't happen very often (less than once per second).
482//! - All addresses passed to the keeper and backends will be valid network addresses
483//!
484//! Again, some of these assumptions won't stay true for Lab 3, so try to avoid
485//! relying on these assumptions if possible.
486//!
487//! ## Requirements
488//!
489//! In addition to the requirements specified by the interfaces, your
490//! implementation should also satisfy the following requirements:
491//!
492//! - When the Tribbler service function call has valid arguments, the function
493//! call should not return an error.
494//! - The front-end should be stateless and hence safe to be kill at anytime.
495//! - The back-ends should be scalable, and the front-end should use the
496//! back-ends in a scalable way. This means that when the back-end is the
497//! throughput bottleneck, adding more back-ends should (with high
498//! probability) mitigate the bottleneck and lead to better overall system
499//! performance.
500//! - When running with back-ends on multiple machines (assuming all the
501//! back-ends satisfy the performance assumptions), every Tribbler service
502//! call should return within three seconds. Ideally much less than this.
503//! - Each back-end should maintain the same key-value pair semantics as in Lab
504//! 1.
505//!
506//! As a result, all test cases that pass for Lab 1's storage implementation
507//! should also pass on your bin storage implementation for Lab 2.
508//!
509//! ## Building Hints
510//!
511//! While you are free to build the system any way that meets the requirements,
512//! here are some suggestions:
513//!
514//! - For each service call in the front-end, if it updates anything in the
515//! back-end storage, use only one write-RPC call to commit your change. This
516//! will make sure that the call will either succeed or fail, and not end up
517//! in some weird intermediate state. You might issue more write calls
518//! afterwards, but those should be only soft hints, meaning that if
519//! subsequent writes don't succeed, the storage system is not left in an
520//! inconsistent state.
521//! - Hash the tribbles and other information into all the back-ends based on
522//! username.
523//! - Synchronize the logical clocks among all the back-ends every second. (This
524//! will also serve as a heart-beat signal, which will be useful for
525//! implementing Lab 3.) However, you should not try to synchronize the clocks
526//! for every post, because that will be not scalable.
527//! - Do some garbage collection when one user has too many tribbles saved in
528//! the storage. Recall that no one will ever see more than the most recent
529//! 100 tribbles by any other user.
530//! - Keep multiple caches for the `list_users()` call when the users are many.
531//! Note that when the user count is more than 20, you don't need to track new
532//! registered users anymore.
533//! - Keep a log for the users that a user follows, where each log entry is an
534//! action of `follow()` or `unfollow()`.
535//!
536//! ## Possible Mistakes
537//!
538//! Here are some possible mistakes that a lazy and quick but incorrect
539//! implementation might do:
540//!
541//! - **Read-modify-write**: As an example, a tribbler might read a counter from
542//! the key-value store, increment it by one, and then write it back (at the
543//! same key). This will introduce a race condition among the front-ends.
544//! - **Not handling errors**: A tribbler call might require several RPC calls
545//! to the back-end. It is important to properly handle *any* error returned
546//! by these calls. It is okay to tell the user that an error occurred.
547//! However, it's not a good plan to leave your back-end inconsistent.
548//! - **Sorting by the timestamps first**: Again, the Tribble Order dictates
549//! that the logic clock is the first field to consider on sorting.
550//! - **Misusing the clock argument in Post()**: For example, you might directly
551//! use that argument as the new post's clock field. Intuitively, the clock
552//! argument tells the *latest* tribble a user has seen (which might be 0 if
553//! the user hasn't seen any tribbles yet), hence the new posted tribble
554//! better have a clock value that is at least larger than the argument. You
555//! might need to do more than increment it by one though.
556//! - **Generating the clock from the timestamp**: While 64-bit clock can cover
557//! a very wide time range even in the unit of nanoseconds, you should keep in
558//! mind that the front-ends are running on different servers with arbitrary
559//! physical time differences. It is not wise to generate the logical *clock*
560//! from the physical *time*.
561//! - **Not handling old tribbles**: Note that only the most recent 100 tribbles
562//! of a user matter. Not handling old tribbles might lead to worse and worse
563//! performance over time and eventually break your performance promises.
564//!
565//! ## `Report.md`
566//!
567//! Please include a `Report.md` file. `Report.md` should be a text file
568//! (ideally in [Markdown](https://daringfireball.net/projects/markdown/syntax)
569//! format) that describes how your system works at a high level. You should be
570//! able to describe how your Bin Storage works in a few sentences, and Tribbler
571//! in a few more.
572//!
573//! The purpose of this document is two-fold:
574//! - If your `Report` is well written, it'll make our job of grading
575//! *considerably* easier.
576//! - An overview document forces you to think about your overall strategy, and
577//! forces you to try to find an elegant solution (that you can describe
578//! succinctly).
579//!
580//! This `Report` *will* count towards your grade, but *will not* be a
581//! significant number of points overall. It's worth spending some time on, but
582//! NOT at the expense of good, working code. You'll primarily be graded on
583//! clarity and coverage of the big ideas of your solutions.
584//!
585//! ## Turning In Your Code
586//!
587//! Instructions for turning in the assignment are provided in the lab
588//! repository.
589//!
590//! ## Happy Lab 2!
591//!
592mod lab;
593pub use crate::lab2::lab::new_bin_client;
594pub use crate::lab2::lab::new_front;
595pub use crate::lab2::lab::serve_keeper;