openlang

Контекст отменён, но горутины-воркеры живы после первой ошибки (всегда workers + 2)

Всем привет,

Пишу небольшой CLI на Go, shotcheck, который проверяет каждый PNG в папке на точное соответствие размеру, который нужен слоту в App Store. Пул воркеров (по умолчанию 8) читает имена из jobs, проверяет файлы и отправляет Result в небуферизованный канал results. collect в main читает их, и на первом же плохом результате вызывает cancel() и возвращает ошибку. Видимое поведение правильное: печатается checked 11 of 40, потом FAIL с именем файла, код выхода 1.

Проблема у меня с флагом -debug. После ошибки я печатаю runtime.NumGoroutine() после короткого sleep и ожидаю 1. Получаю 10 при 8 воркерах и 6 с -workers 4, то есть всегда workers + 2. Более долгий sleep (2 с, потом 5 с) ничего не меняет. Пишу по памяти о последнем запуске, потому что сейчас не могу запустить Go на этой машине.

Горутина-продюсер останавливается и jobs закрывается, так что я не понимаю, почему отменённый контекст не остановил остальных. Пробовал переставлять cancel(), без изменений. Подозреваю WaitGroup или то, что defer cancel() срабатывает слишком поздно.

В CLI это безвредно, потому что процесс завершается. Но я хочу использовать этот пул в долгоживущем CI-хелпере, и там, по-моему, эти горутины будут копиться при каждом упавшем запуске и сильно тормозить машину.

Часть с воркерами:

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. Ссылка на ответ

    Спасибо, fenwick40, думаю, вы правы. После возврата из collect из results никто не читает, а канал небуферизованный, так что каждый воркер, у которого уже есть результат, навсегда блокируется на results <- r. WaitGroup никогда не доходит до нуля, поэтому close(results) не выполняется. Это и есть «+ 2»: горутина, ждущая wg.Wait(), и, вероятно, ещё один хелпер. А cancel ничего не дал, потому что воркеры не смотрят на ctx, пока заблокированы на отправке.

    Значит, фикс в том, чтобы отправка учитывала контекст:

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

    Ещё надо проверять ctx.Done() в цикле перед checkShot. Сейчас не могу запустить, попробую завтра и отпишусь.

    Про «сильно тормозить машину»: наверное, я преувеличил, припаркованная горутина стоит дёшево, но в CI-хелпере они всё равно будут утекать при каждом упавшем запуске, так что чинить стоит))

    Переведено с английскогоПоказать оригинал

Другие стороны того же вопроса

  1. Пометка фильтра как async не помогла с фризом

    ledgerscan, поиск по 400k записей. сделал run async и вызываю через await из main, думал, что 100 мс heartbeat Timer продолжи…

    Ответы 2Последняя активность dart
  2. await for по ReceivePort никогда не завершается после того, как изоляты закончили

    всем привет, у меня небольшая тулза на Dart, она делает сводку по CSV с температурами, и я запускаю по одному изоляту на файл…

    Ответы 2Последняя активность dart
  3. В outliers.log строк больше, чем в счётчике :outliers, 8 futures

    Я сворачиваю 20000 симулированных показаний приливов в один atom из 8 futures. Каждое показание выше порога ещё и дописываетс…

    Ответы 1Последняя активность clojure