Fluent Rust - Mutex Poisoning

Migo·2023년 9월 17일
0

Fluent Rust

목록 보기
8/23
post-thumbnail

Mutex

Mutex는 공유자원 관리를 위한 API를 제공하는 구조체이다. Rust뿐만 아니라 많은 언어들에서, Mutex에 의해 wrapping되어 있는 데이터에 대한 접근은, 이미 Lock을 획득한 다른 Thread가 있는 경우 접근하는 Thread에 대해 os레벨의 sleep을 거는 방식으로 CPU clock에 대한 손실을 최소화한다. 요약하자면

  • Lock이 획득된 적이 없는 경우 -> 바로 데이터 접근
  • 이미 다른 Thread에 의해 Lock이 획득 되었고, 아직 Unlock되지 않은 경우 -> Os sleep

아래 코드는 Mutex 사용 예시이다.

	// create mutex wrapped in Arc
    let mutex = Arc::new(Mutex::new(0));

	// Put it to background thread
    let _ = std::thread::spawn({
        let mutex = mutex.clone();
        move || {
            let mut value = mutex.lock().unwrap();
            
            // get update value
            *value = 1;

        }
    });


    sleep(Duration::from_secs(1));

	// get updated value
    let updated_value = mutex.lock().unwrap();
    println!("{}", *updated_value);

Poisoning

Mutex poisoningmutexed value의 일관성을 보장하기 위한 매커니즘이다. 한 쓰레드에서 mutex value를 얻은 후 panic이 일어난 경우, 해당 Mutex에 접근 시, PoisonError를 반환하게 된다.

    let mutex = Arc::new(Mutex::new(0));

    let _ = std::thread::spawn({
        let mutex = mutex.clone();
        move || {
            let mut value = mutex.lock().unwrap();
            *value = 1;
            panic!("Panic Point")
        }
    });
    
    sleep(Duration::from_secs(1));
    
    if let Err(_) = mutex.lock() {
        println!("poisoned!");
    };

Case where data consistency is not really a matter

값의 일관성은 대부분의 경우 굉장히 중요한 문제이지만, 특정 thread에서 panic이 발생하면 어떤 일이 일어났던지 poison 마킹을 하는 이러한 방식은 아래의 케이스에서 문제를 발생시킬수 있다.

Case1: 아직 Mutexed value를 변경하지 않았지만 Guard만 얻어 놓은 경우


    let _ = std::thread::spawn({
        let mutex = mutex.clone();
        move || {
            let mut value = mutex.lock().unwrap();
            //*value = 1; 
            panic!("Panic Point")
        }
    });

위 처럼, Mutex를 얻었지만 값을 변경하지 않고 read만 thread에서, 그리고 그 과정에서 panic이 발생한 경우, 다른 thread에서 해당 값에 접근시 PoisonError를 일으키면, 해당 값에 접근 하는 모든 thread에서 unwrap() 사용시 패닉을 일으키게 되고, 결국 전체 시스템 장애로 이어지게 될 것이다.

Case2: 전역변수성 Mutex인 경우

가령 Database connection과 같은 경우가 이에 해당한다.

Solution

이러한 경우의 해결법은 아래와 같다.

    let _ = std::thread::spawn({
        let mutex = mutex.clone();
        move || {
            let mut value = mutex.lock().unwrap();
            panic!("Panic Point")
        }
    });
   
    let value = mutex.lock().unwrap_or_else(PoisonError::into_inner);
    println!("value: {}", value);
    
    

Real Solution

하지만 언제나 그렇듯이, 가장 좋은 해결법은 문제가 발생되지 않도록 하는 것이다. 예방적 차원에서 위의 접근방법은 고려될 수 있으나, 근원적으로 Panic이 아닌, 사용자의 정의에 따라 Result를 적극 활용하여, 모든 타입이 처리될 수 있도록 하자.

profile
Dude with existential crisis

0개의 댓글