포기하는 supervisor

깨끗한 shutdown은 모든 worker가 언젠가 끝난다는 것을 전제했다. 다음 probe는 한 worker를 의도적으로 행 걸리게 했다 —— 그러자, 결코 도착하지 않는 수신을 블록해 기다리는 collector가 run 전체를 길동무 삼아 행에 걸렸다. supervisor에게 필요한 것은 포기할 권리: 마감이 붙은 수신, N밀리초까지 기다리고 영원히 기다리는 대신 None을 반환하는 것. 여섯 중 하나의 stuck job의 비용은, 이제 장부의 timeout 한 줄이지 프로그램이 아니다.

mereconcurrencytimeoutchannelslanguage-design

앞 이야기는 channel에 끝나는 법을 가르쳤다. 그것이 커버하는 것은 worker가 일을 끝내고 빠져 나가는 경우 —— 협력적인 끝이다. 이 이야기는 비협력적인 쪽에 대해서다: 끝나지도 실패하지도 않고, 그저 영원히 걸리는 worker. 모든 분산 시스템과 모든 스레드 풀이 언젠가 이 worker를 만난다. 그리고 그 만남이 밝히는 것은, 언어가 수신의 어느 쪽을 지켜 왔는가다.

하나의 stuck job

probe는 worker pool을 재사용해 파괴 공작했다: 여섯 jobs, 그리고 3번 job만 2초 잔다. 나머지는 전부 10밀리초 —— 결코 돌아오지 않는 네트워크 호출, 사라진 디스크, 엣지 케이스를 밟은 루프의 대역이다. collector의 로직은 자명한 channel_recv results 여섯 번. 다섯 결과는 즉시 도착한다. 여섯째는 결코 오지 않고, collector는 그것을 블록해 기다린다 —— 영원히. 앞 이야기의 기구는 모두 설계대로 동작하고 있다: jobs channel은 깨끗이 닫히고, 다섯 workers는 빠져나가 return하고, join도 성공할 것이다. 하지만 프로그램 전체는 하나의 블로킹 수신에서 행에 걸려, 가장 느린 멤버의 인질이 된다. channel을 닫아도 이것은 고칠 수 없다 —— job이 stuck이라고 아직 아무도 모르므로, 아무도 무엇을 닫아야 할지 모른다. collector에게 빠진 것은 정보가 아니다. 포기할 권리다.

마감이 붙은 수신

**channel_recv_timeout**은 channel과 밀리초 예산을 받아, 그 길이까지 값을 블록 대기하고, timeout에 None을 반환한다 —— closed & drained에서도 None이므로, 시한 없는 판의 shutdown 거동도 포섭한다. collector는 300밀리초 예산으로 여섯 번 시도하는 루프가 된다: Some이 다섯 번, None이 한 번, 그리고 run은 행 대신 장부 —— results=5 timeouts=1 —— 로 끝난다. C backend의 구현은 교과서 그대로, 절대 마감에 대한 pthread_cond_timedwait. reference interpreter는 밀리초 입도의 poll이다. OCaml 표준 라이브러리에 시한부 조건 대기가 없기 때문이고, 소스의 주석은 그렇게 솔직히 말하고 있다. 「끝」 문제의 또 한 조각은, 이미 풀려 있었음이 판명됐다: supervisor가 stuck worker를 알아챈 뒤의 자연스러운 다음 수는 배치의 나머지를 취소하는 것인데 —— 그것은 첫 이야기의 channel_close를 jobs channel에 거는 것뿐이다. 새 primitive는 필요 없다. 거기서 자명한 물음이 섰다: 고전적 병행 툴킷에는, 이 언어에 없는 유명한 primitive가 하나 더 있다 —— 여러 channel을 동시에 기다리는 것이다. 다음 이야기는 그것을 필요로 하는 프로그램을 찾으러 가서, primitive보다 좋은 것을 들고 돌아온다.

← Back to Mere: 언어를 만들다