Нужен пул воркеров, который переживает панику в задачах. Если функция паникует, воркер ловит панику, отправляет ошибку в канал errs и берёт следующую задачу. Остальные воркеры и сам пул об этом не узнают.
type SafePool struct {
// ...
}
// NewSafePool создаёт пул из workers воркеров, которые читают задачи из tasks.
func NewSafePool(workers int, tasks <-chan func(), errs chan<- error) *SafePool
// Start запускает воркеров.
func (p *SafePool) Start()
// Wait возвращается, когда tasks закрыт, все задачи из него обработаны
// и все воркеры завершились.
func (p *SafePool) Wait()
Что должно получиться:
- каждая задача выполняется под защитой
recover, паника одной задачи не останавливает воркера и не трогает остальные;
- на каждую паникующую задачу в
errs приходит ровно одна ошибка (текст любой), на нормальную задачу ошибок нет;
- все нормальные задачи выполняются полностью, в том числе те, что идут после паникующих;
Wait() не закрывает tasks: это делает тот, кто их отправляет. Когда он закроет канал и воркеры дочитают остаток, Wait() вернётся;
- нет гонок под
-race и нет утечек горутин.
Пример. В канал положили 3 задачи: func() {}, func() { panic("boom") }, func() {}, затем закрыли его. Пул из одного воркера выполнит первую и третью задачу, а из errs можно прочитать ровно одну ошибку.
Два замечания про errs. Пул отправляет в него без таймаутов, поэтому либо у канала достаточный буфер, либо кто-то его читает параллельно, иначе воркер заблокируется на отправке и Wait() не вернётся. И закрывать errs должен вызывающий, после Wait(), когда воркеры уже ничего не отправят.
Тесты именно так и устроены: 300 нормальных и 100 паникующих задач при 8 воркерах, затем 50 пар «паника, нормальная задача» при одном воркере. Во втором тесте проверяется, что воркер действительно продолжает работу после паники. Дальше: паники разных видов (строка, error, nil map, выход за границы, panic(nil)) дают по одной непустой ошибке; Wait() не возвращается, пока tasks не закрыт, и не закрывает его сам; после Wait() лишних горутин нет; четыре взаимно ждущие задачи завершаются только на четырёх параллельных воркерах.