Skip to content

Commit

Permalink
docs: add semaphore example
Browse files Browse the repository at this point in the history
Signed-off-by: Muhan Song <songmuhan@stu.pku.edu.cn>
  • Loading branch information
songmuhan committed Oct 1, 2023
1 parent 453c720 commit 6b24580
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
1 change: 1 addition & 0 deletions tokio/Cargo.toml
Expand Up @@ -98,6 +98,7 @@ bytes = { version = "1.0.0", optional = true }
mio = { version = "0.8.6", optional = true, default-features = false }
num_cpus = { version = "1.8.0", optional = true }
parking_lot = { version = "0.12.0", optional = true }
lazy_static = "1.4.0"

[target.'cfg(not(target_family = "wasm"))'.dependencies]
socket2 = { version = "0.5.3", optional = true, features = [ "all" ] }
Expand Down
114 changes: 114 additions & 0 deletions tokio/src/sync/semaphore.rs
Expand Up @@ -123,6 +123,120 @@ use std::sync::Arc;
/// # }
/// ```
///
/// ## Prevent tests from running in parallel
///
/// By default, Rust runs tests in the same file in parallel. However, in some cases, running two tests in parallel may lead to problems.
/// For example, this can happen when tests use the same database.
///
/// Consider the following scenario:
/// 1. `test_insert`: Inserts a key-value pair into the database, then retrieves the value using the same key to verify the insertion.
/// 2. `test_update`: Inserts a key, then updates the key to a new value and verifies that the value has been accurately updated.
/// 3. `test_others`: A third test that doesn't modify the database state. It can run in parallel with the other tests.
///
/// In this example, `test_insert` and `test_update` need to run in sequence to work, but it doesn't matter which test runs first.
/// We can leverage a semaphore with a single permit to address this challenge.
///
/// ```
/// use tokio::sync::{Semaphore, Mutex};
/// use lazy_static::lazy_static;
/// # use std::collections::HashMap;
/// # struct Database {
/// # map: Mutex<HashMap<String, i32>>,
/// # }
/// # impl Database {
/// # pub fn setup() -> Database {
/// # Database {
/// # map: Mutex::new(HashMap::new()),
/// # }
/// # }
/// # pub async fn insert(&self, key: &str, value: i32) {
/// # self.map.lock().await.insert(key.to_string(), value);
/// # }
/// # pub async fn update(&self, key: &str, value: i32) {
/// # self.map.lock().await
/// # .entry(key.to_string())
/// # .and_modify(|origin| *origin = value);
/// # }
/// # pub async fn delete(&self, key: &str) {
/// # self.map.lock().await.remove(key);
/// # }
/// # pub async fn get(&self, key: &str) -> i32 {
/// # *self.map.lock().await.get(key).unwrap()
/// # }
/// # }
///
/// // Initialize a static semaphore with only one permit, which is used to
/// // prevent test_insert and test_update from running in parallel.
/// static PERMIT: Semaphore = Semaphore::const_new(1);
///
/// // Initialize the database that will be used by the subsequent tests.
/// lazy_static! {
/// static ref DB: Database = Database::setup();
/// }
/// #[test]
/// # fn fake_test() {}
/// async fn test_insert() {
///
/// // Acquire permit before proceeding. Since the semaphore has only one permit,
/// // the test will wait if the permit is already acquired by other tests.
/// let permit = PERMIT.acquire().await.unwrap();
///
/// // Do the actual test stuff with database
///
/// // Insert a key-value pair to database
/// let (key, value) = ("name", 0);
/// DB.insert(key, value).await;
///
/// // Verify if the value has been inserted correctly.
/// assert_eq!(DB.get(key).await, value);
///
/// // Undo the insertation, make database status unchanged.
/// DB.delete(key).await;
///
/// // Drop permit, make possible waiting test to run.
/// drop(permit);
/// }
/// #[test]
/// # fn fake_test() {}
/// async fn test_update() {
///
/// // Acquire permit before proceeding. Since the semaphore has only one permit,
/// // the test will wait if the permit is already acquired by other tests.
/// let permit = PERMIT.acquire().await.unwrap();
///
/// // Do the same insert.
/// let (key, value) = ("name", 0);
/// DB.insert(key, value).await;
///
/// // Update the existing value with a new one.
/// let new_value = 1;
/// DB.update(key, new_value).await;
///
/// // Verify if the value has been updated correctly.
/// assert_eq!(DB.get(key).await, new_value);
///
/// // Undo any modificattion.
/// DB.delete(key).await;
///
/// // Drop permit, make possible waiting test to run.
/// drop(permit);
/// }
/// #[test]
/// # fn fake_test() {}
/// async fn test_others() {
/// // this test can run in parallel with test_insert and test_update.
/// // They have nothing to do with PERMIT.
/// }
///
/// # #[tokio::main]
/// # async fn main() {
/// # test_insert().await;
/// # test_update().await;
/// # test_others().await;
/// # }
///
/// ```
///
/// ## Rate limiting using a token bucket
///
/// Many applications and systems have constraints on the rate at which certain
Expand Down

0 comments on commit 6b24580

Please sign in to comment.