openlang

context를 취소했는데도 첫 에러 이후 worker goroutine이 계속 살아 있어요 (항상 workers + 2)

안녕하세요,

작은 Go CLI인 shotcheck를 만들고 있어요. 폴더 안의 모든 PNG가 App Store 슬롯에서 요구하는 정확한 크기인지 확인하는 도구예요. worker 풀(기본 8개)이 jobs에서 이름을 읽어서 검사하고, Result를 버퍼 없는 results 채널로 보내요. main의 collect가 이걸 읽다가 처음으로 잘못된 게 나오면 cancel()을 호출하고 에러를 반환해요. 눈에 보이는 동작은 맞아요. checked 11 of 40을 출력하고, 파일 이름과 함께 FAIL, 종료 코드 1이 나와요.

문제는 -debug 플래그예요. 실패한 뒤에 잠깐 sleep하고 runtime.NumGoroutine()을 출력하는데, 1이 나올 거라고 예상했어요. 그런데 worker가 8개면 10이 나오고, -workers 4면 6이 나와요. 그러니까 항상 workers + 2예요. sleep을 더 길게(2초, 그다음 5초) 해봐도 달라지지 않아요. 지금은 이 컴퓨터에서 Go를 못 돌려서, 마지막으로 실행했던 기억으로 쓰고 있어요.

producer goroutine은 멈추고 jobs도 닫히는데, 취소된 context가 왜 나머지를 멈추지 못했는지 모르겠어요. cancel() 위치를 이리저리 옮겨 봤는데 그대로예요. WaitGroup이 의심스럽거나, defer cancel()이 너무 늦게 실행되는 건가 싶어요.

CLI에서는 프로세스가 끝나니까 문제가 없어요. 하지만 이 풀을 오래 도는 CI 헬퍼에 넣고 싶은데, 그러면 실패한 실행마다 이 goroutine들이 쌓여서 머신이 많이 느려질 것 같아요.

worker 부분이에요:

for i := 0; i < n; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		for path := range jobs {
			r := checkShot(path)
			results <- r
		}
	}()
}

go func() {
	wg.Wait()
	close(results)
}()

제가 뭔가 간단한 걸 놓치고 있는 걸까요? 읽어 주셔서 감사해요))

영어에서 번역원문 보기

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    거의 확실히 worker들이 results <- r에서 막혀 있는 거예요. collect가 반환된 뒤에는 누가 그 채널을 읽나요?

    영어에서 번역원문 보기
  2. 고유 링크

    고마워요, fenwick40. 맞는 것 같아요. collect가 반환되면 results를 읽는 쪽이 없고 채널은 버퍼가 없어서, 이미 결과를 들고 있는 worker는 전부 results <- r에서 영원히 막혀요. 그러면 WaitGroup이 0에 도달하지 않고, close(results)도 실행되지 않아요. 그게 "+ 2"일 거예요. wg.Wait()에서 기다리는 goroutine하나, 그리고 아마 도우미 goroutine 하나요. 그리고 worker들이 send에서 막혀 있는 동안에는 ctx를 보지 않으니까 cancel이 아무 효과가 없었던 거고요.

    그래서 send가 context를 따르게 고치면 돼요:

    select {
    case results <- r:
    case <-ctx.Done():
        return
    }

    checkShot 전에 루프에서 ctx.Done()도 확인해야 해요. 지금은 못 돌려 봐서 내일 해보고 다시 알려 드릴게요.

    "머신이 많이 느려질 것 같다"는 건 아마 제가 과장했던 것 같아요. 멈춰 있는 goroutine은 가볍긴 한데, CI 헬퍼에서는 실패한 실행마다 계속 새니까 그래도 고칠 가치는 있어요))

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. filter를 async로 바꿔도 멈춤 현상은 그대로예요

    ledgerscan으로 400k개 항목을 검색해요. run을 async로 바꾸고 main에서 await했어요. 100 ms 하트비트 Timer가 계속 돌 줄 알았거든요. 그런데 실제로는 틱이 찍히고, searching "re…

    답글 2최근 활동 dart
  2. await for over ReceivePort가 isolate가 다 끝나도 끝나지 않아요

    안녕하세요, 온도 CSV를 요약해 주는 작은 dart 툴을 만들고 있어요. 큰 파일들이 서로 기다리지 않도록 파일마다 isolate를 하나씩 띄워요. WSL에서 이렇게 실행해요: dart run bin/summarize.da…

    답글 2최근 활동 dart
  3. outliers.log의 줄 수가 :outliers 카운터보다 많아요, future 8개

    8개의 future에서 시뮬레이션된 조수 측정값 20000개를 하나의 atom에 fold해요. 임계값을 넘는 측정값은 모두 outliers.log에도 append하고요. 요약은 문제없어요. :n은 항상 20000이고, :ou…

    답글 1최근 활동 clojure