Hacker News 의견
  • send/recv에 timeout을 두는 예제가 매우 흥미롭다고 생각함, 실행되지 않은 상태에서 바로 폴링 없이 future가 실행되는 언어에서는 오히려 반대 상황이 나올 수 있음을 알게 됨, send에 timeout을 두면 timeout 이후에도 메시지가 전송될 수 있으나 메시지가 유실되지는 않아 안전하지만, recv에 timeout을 두면 채널에서 메시지를 읽은 뒤 timeout이 선택되는 상황에서 메시지를 그냥 버려서 안전하지 않을 수 있음, 해결책은 timeout이나 채널에서 '무언가 사용 가능함'을 선택하도록 하고, 후자의 경우 peek을 통해 데이터를 안전하게 보는 것임
    • 이게 바로 cancellation-safety의 핵심임이 아닌지 생각 중임
    • 좋은 지적이라 생각함
  • 이 주제에 관해 내가 쓴 자료 몇 가지를 소개하고 싶음
    • async 함수가 반드시 끝까지 실행돼야 한다는 제안서를 2020년에 작성했었음, graceful cancellation 기능이 포함되고, 아직까지 더 나은 아이디어가 나오지 않았다고 생각함 제안서 링크
    • sync와 async Rust 전반에 걸쳐 unified cancellation을 위한 제안도 있음 ("A case for CancellationTokens") gist 링크
    • 위 내용을 실제 구현한 사례도 있음 min_cancel_token
  • futures가 취소되는 게 무슨 문제인지 잘 모르겠음, futures는 task가 아니고, 해당 글에서도 내부적으로 이 점을 인정함, 그렇다면 future가 끝까지 실행되지 않더라도 원래 그런 것 아닌지, 그리고 그런 상황이 왜 문제인지 이해 못 하겠음, 예제에서 "cancel unsafe" future라 주장하지만, 핵심은 기대와 현실의 오해라 생각됨
    • 예제1은 try_join 중 하나가 에러로 cancel
    • 예제2는 취소될 때 데이터 미기록
      이런 사례 모두 context가 cancel되어서 작업이 완료되지 않는 것은 당연한 동작임, 작업이 반드시 끝나야 한다면 독립 task로 분리하면 될 일임, 나는 뭔가 중요한 nuance를 놓치고 있는 것일지 의문임, 원래 work가 cancellation에 의해 없어지는 게 futures의 설계 의도라고 이해하는데, 뭐가 문제인지 다시 짚어주면 좋겠음
    • 맞는 말임! 실제로 Oxide에서 이로 인해 많은 버그가 생긴 적 있음, futures가 passive하게 await 지점마다 언제든 취소될 수 있다는 점을 충분히 이해하면 남는 건 세세한 테크닉임
  • RustConf에서 이 발표를 정말 재미있게 들었음, cancel safety와 cancel correctness의 개념 구분이 정말 유용함, 발표가 블로그 포스트로도 올라와서 너무 좋음, 발표는 좋지만 블로그로 정리된 게 공유와 참조에 더 용이함
    • "cancel correctness"라는 표현이 cancellation의 맥락을 잘 잡아줘서 마음에 듦, 반면 "cancel safety"라는 용어는 별로 좋아하지 않음, Rust의 safety 개념과도 딱 맞지 않고, 불필요하게 판단적인 느낌임, safe/unsafe가 더 좋거나 나쁜 걸 암시하나 cancel 동작의 바람직함은 상황마다 다름, 예를 들면, spawn된 task를 기다리는 future는 "cancellation safe"라 불리지만 drop 시 task가 계속 실행되면 필요 없는 일이 쌓이고 lock이나 port도 점유해서 문제될 수 있음, 오히려 drop 시 task를 멈추는 spawn handle은 "cancellation unsafe"라곤 하지만 dependent task의 cleanup엔 매우 중요한 패턴임
    • 블로그 글이 더 읽기 쉽고 좋다고 생각함, 공감함
  • https://sunshowers.io/posts/cancelling-async-rust/#the-pain-of-tokio-mutexes의 내용이 특히 흥미로웠음, 나도 쉽게 저런 실수를 할 것 같음
    • 나는 Go 개발자임에도 이런 부분이 도움됨, Rust는 도구가 더 엄격하게 도와주지만, goroutines, 채널, select, 그 외 동시성 primitives에서 Go에서도 같은 함정에 빠지기 쉬움
  • 처음 예제에서 원하는 동작이 뭔지 불명확함, 큐가 꽉 차면 드롭, 대기, 패닉 중에서 선택 필요함, 블로킹에 타임아웃 거는 건 주로 데드락 감지임, 코드가 "모든 메시지가 채널로 가지 않는다"고 말하는데, 당연히 리소스가 부족하면 그럴 수밖에 없음, 목적이 뭐지? 깔끔한 프로그램 종료? 그건 스레드 환경에서 상당히 어려움, async에서도 쉽지 않음, 실제 유즈케이스는 원격지와의 메시지 교환에서 상대가 끊길 때 내 쪽 상태를 정리하는 것임
    • 이상적으로는 채널에 공간이 날 때까지 메시지를 버퍼에 보관하고 싶음, 이 내용은 발표 후반부 "What can be done"에서 다룸
    • 예시에 답이 있음, 5초간 공간 없을 때 로깅하는 코드는 진단 용도인데, 이게 은근 데이터 유실로 이어질 위험이 있음, 조금 인위적이긴 하지만 실제로 "왜 동작 안 하지?" 같은 문제 대응 코드로 시스템 곳곳에 붙이기 쉬움
    • 참고로 이 글 작성자는 they/she 대명사를 사용함 about
  • await는 언제나 잠재적 리턴 포인트임을 항상 염두에 둬야 함, 반드시 같이 atomic하게 실행돼야 하는 두 액션 사이에 await를 두는 것은 피하는 게 좋음
    • 이게 실제로 어떻게 문제를 일으키는지 궁금함 예를 들면,
      async fn a() {
        b().await
      }
      async fn b() {
        c().await
        d().await
      }
      async fn c() {}
      async fn d() {}
      
      이 코드에서 어떤 식으로 d가 호출되지 않는 문제가 생기는지? c에서 취소가 발생해서? 아니면 a에서 상위에서 뭔가 생겨서?
    • 그럼 이거 좀 위험함 아닌지? 물론 어쩔 수 없는 부분이겠지만 "critical section"에 await가 두 번 있으면 그 사이에 일시정지되지만 결국 이어서 실행돼야만 하는 상황이 있을 수 있음, 예를 들어 DB 변경 후 audit log를 남길 때 둘 다 꼭 Execution되어야 한다면, 그냥 do not cancel 주석 달기밖에 답이 없는지 궁금함
  • Rust의 Future는 C++에서 move semantics처럼, Future가 끝난 뒤에는 invalid state가 될 수도 있음, Rust는 stackless coroutine 설계이기 때문에 poll 기반 async 구조를 직접 구현할 때 상태를 struct에 직접 관리해야 함, 이런 점이 모두 흔한 함정임, 그리고 최근 async Rust에서 cancellation은 state management에 새로운 변수임, 내가 mea(Make Easy Async) 라이브러리를 개발할 때도 cancel safety가 trivial하지 않으면 꼭 문서화함, 그리고 경솔한 async cancellation로 IO stack에 문제가 생긴 사례가 기억남 mea reddit 사례
  • 정말 좋은 발표였음! 완전 초짜인 나는 SOP에서는 Future를 cancel할 수 없다는 점을 미리 강조해 줬으면 했음, .await가 future를 소유해서 drop() 불가, future가 lazy하니 .await 뒤에는 취소가 어떻게 되는지 명확하지 않았음, 이후 select!와 Abortable()를 조사해보고 이해했지만, 앞으로 발표한다면 맨 처음에 이 부분도 콜아웃 해주면 완벽할 것 같음
    • 질문. 여기서 SOP가 뭘 의미하는지 궁금함
  • 정말 타이밍이 좋았음, 오늘 막 새로운 함수의 doc comment에 "이 함수는 cancel safe임"을 붙이고 있었는데, 이런 고민을 함, 어서 async drop이 가능해졌으면 함
    • 그 함수가 궁금함, 좀 더 설명해 줄 수 있는지 curiosity 있음