동시성 연습 문서의 문제 2 풀이 코드다.

//! 동시성 연습 p2: 잠금 순서가 엇갈려 생기는 교착상태와 그 고침.
//! 엇갈린 순서는 Barrier로 "둘 다 첫 잠금을 쥔 순간"을 만든 뒤 try_lock으로 순환 대기를 확인한다(진짜로 멈추지 않게).
//! 고친 판은 4 스레드 × 10,000번을 돌려 멈추지 않고 합계가 맞는 것을 본다.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/35_concurrency-practice_p2.rs"

use std::sync::{Barrier, Mutex, TryLockError};
use std::thread;

struct Shop {
    inventory: Mutex<i64>, // 재고 개수
    ledger: Mutex<i64>,    // 현금
}

/// 엇갈린 순서를 그대로 흉내 낸다: sell은 inventory→ledger, restock은 ledger→inventory.
/// 둘 다 첫 잠금을 쥔 뒤 두 번째 잠금을 try_lock으로 시도해 막히는지 돌려준다.
fn crossed_order_blocks(shop: &Shop) -> (bool, bool) {
    let barrier = Barrier::new(2);
    thread::scope(|s| {
        let sell = s.spawn(|| {
            let _inv = shop.inventory.lock().unwrap();
            barrier.wait(); // 상대도 첫 잠금을 쥐었다
            let blocked = matches!(shop.ledger.try_lock(), Err(TryLockError::WouldBlock));
            barrier.wait(); // 상대도 시도할 때까지 첫 잠금을 쥐고 있는다
            blocked
        });
        let restock = s.spawn(|| {
            let _led = shop.ledger.lock().unwrap();
            barrier.wait();
            let blocked = matches!(shop.inventory.try_lock(), Err(TryLockError::WouldBlock));
            barrier.wait();
            blocked
        });
        (sell.join().unwrap(), restock.join().unwrap())
    })
}

/// 고친 판: 두 함수 모두 inventory → ledger 순서로 잠근다.
fn sell(shop: &Shop, price: i64) {
    let mut inv = shop.inventory.lock().unwrap();
    let mut cash = shop.ledger.lock().unwrap();
    *inv -= 1;
    *cash += price;
}

fn restock(shop: &Shop, cost: i64) {
    let mut inv = shop.inventory.lock().unwrap(); // 원래는 ledger를 먼저 잠갔다
    let mut cash = shop.ledger.lock().unwrap();
    *cash -= cost;
    *inv += 1;
}

fn stress(threads: usize, rounds: usize) -> (i64, i64) {
    let shop = Shop {
        inventory: Mutex::new(100),
        ledger: Mutex::new(0),
    };
    thread::scope(|s| {
        for t in 0..threads {
            let shop = &shop;
            s.spawn(move || {
                for _ in 0..rounds {
                    if t % 2 == 0 {
                        sell(shop, 3);
                    } else {
                        restock(shop, 2);
                    }
                }
            });
        }
    });
    (
        shop.inventory.into_inner().unwrap(),
        shop.ledger.into_inner().unwrap(),
    )
}

fn main() {
    let shop = Shop {
        inventory: Mutex::new(0),
        ledger: Mutex::new(0),
    };
    let (a, b) = crossed_order_blocks(&shop);
    assert!(a && b);
    println!("crossed order: sell waits for ledger, restock waits for inventory -> circular wait");

    let (inv, cash) = stress(4, 10_000);
    // 판매 2 스레드 × 10,000 = 20,000번, 입고 2 스레드 × 10,000 = 20,000번
    assert_eq!(inv, 100 - 20_000 + 20_000);
    assert_eq!(cash, 20_000 * 3 - 20_000 * 2);
    println!("same order: 40,000 ops finished, inventory {inv}, cash {cash}");
    println!("ALL CHECKS PASSED");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn single_ops() {
        let (inv, cash) = stress(1, 5);
        assert_eq!((inv, cash), (95, 15));
        let (inv, cash) = stress(2, 3);
        assert_eq!((inv, cash), (100, 3));
    }
}