async 연습 문서의 문제 2 풀이 코드다.

//! async 연습 p2: async 함수 안의 무거운 계산이 같은 런타임의 다른 요청을 멈추게 하는 것을 재고,
//! spawn_blocking으로 옮겨 고친다.
//! 의존성: tokio = { version = "1", features = ["full"] }
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/40_async-practice_p2.rs"

use std::time::{Duration, Instant};

/// 무거운 계산 흉내: FNV-1a 해시를 구하고, 약 200ms 동안 CPU를 붙잡는다(.await 없음).
fn heavy_hash(data: &[u8]) -> u64 {
    let start = Instant::now();
    let mut h: u64 = 0xcbf2_9ce4_8422_2325;
    for b in data {
        h ^= u64::from(*b);
        h = h.wrapping_mul(0x0100_0000_01b3);
    }
    while start.elapsed() < Duration::from_millis(200) {
        std::hint::spin_loop(); // 계산이 더 오래 걸리는 상황을 흉내 낸다
    }
    h
}

/// 고치기 전: async 함수 안에서 바로 계산한다
async fn checksum_bad(data: Vec<u8>) -> u64 {
    heavy_hash(&data)
}

/// 고친 뒤: 블로킹용 스레드 풀로 보낸다
async fn checksum_good(data: Vec<u8>) -> u64 {
    tokio::task::spawn_blocking(move || heavy_hash(&data))
        .await
        .expect("계산 태스크가 panic하지 않는다")
}

/// 10ms마다 응답해야 하는 가벼운 작업(ping)이 예정보다 늦은 시간의 최댓값(ms)
async fn worst_ping_while<F: std::future::Future<Output = u64> + Send + 'static>(job: F) -> u128 {
    let pinger = tokio::spawn(async {
        let mut worst = 0;
        for _ in 0..30 {
            let t = Instant::now();
            tokio::time::sleep(Duration::from_millis(10)).await;
            worst = worst.max(t.elapsed().as_millis().saturating_sub(10));
        }
        worst
    });
    tokio::time::sleep(Duration::from_millis(20)).await;
    let h = tokio::spawn(job);
    let _ = h.await.unwrap();
    pinger.await.unwrap()
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let data = vec![7u8; 4096];
    let bad = worst_ping_while(checksum_bad(data.clone())).await;
    let good = worst_ping_while(checksum_good(data.clone())).await;
    println!("ping 최대 지연: 고치기 전 {bad}ms, 고친 뒤 {good}ms");
    assert!(bad >= 150);
    assert!(good < 50);
    assert_eq!(checksum_bad(data.clone()).await, checksum_good(data).await);
    println!("ALL CHECKS PASSED");
}