У каждой задачи в пуле свой context.Context: клиент отключился, истёк дедлайн, запрос отменили. Нужно, чтобы отменённая задача не держала воркера, а следующая получала его сразу, как только отменённая вернулась.
Тип CtxTask уже объявлен в support.go, вам нужно реализовать CtxPool:
type CtxTask struct {
Ctx context.Context
Run func(ctx context.Context)
}
// NewCtxPool создаёт пул на workers воркеров.
func NewCtxPool(workers int) *CtxPool
// Submit передаёт задачу пулу.
func (p *CtxPool) Submit(t CtxTask)
// Start запускает воркеры.
func (p *CtxPool) Start()
// Stop дожидается воркеров и завершает пул.
func (p *CtxPool) Stop()
В пуле один воркер. Первая задача ждёт отмены своего контекста. Вторая обычная. Пока первая не отменена, вторая ждёт. После cancel() первая возвращается, воркер свободен, и вторая выполняется:
pool := NewCtxPool(1)
pool.Start()
ctx, cancel := context.WithCancel(context.Background())
pool.Submit(CtxTask{Ctx: ctx, Run: func(c context.Context) {
<-c.Done()
fmt.Println("первая: отменена")
}})
go func() {
time.Sleep(50 * time.Millisecond)
cancel()
}()
pool.Submit(CtxTask{Ctx: context.Background(), Run: func(c context.Context) {
fmt.Println("вторая: выполнена")
}})
pool.Stop()
// первая: отменена
// вторая: выполнена
Start запускает workers воркеров (для workers < 1 считайте, что один).
- Воркер вызывает
Run(ctx) с контекстом именно этой задачи и считается свободным, когда Run вернулась. Принудительно оборвать чужую функцию пул не может: реагировать на ctx.Done() обязана сама Run.
- Задача с неотменённым контекстом выполняется до конца.
Submit может подождать, пока какой-нибудь воркер освободится. Очередь неограниченного размера строить не нужно, тесты вызывают Submit из отдельной горутины, если воркер занят.
- Если контекст задачи уже отменён к моменту, когда воркер её взял,
Run можно не вызывать. Тесты этого не проверяют.
Stop дожидается завершения всех воркеров, горутин после него не остаётся. Повторный Stop не должен паниковать.
- Код проходит
go test -race.
Порядок вызовов: сначала Start, потом Submit. Все Submit завершаются до Stop, после Stop новые задачи не отправляют. Функции Run рано или поздно возвращаются.
Тесты проверяют: обычная задача выполняется; пока единственный воркер занят неотменённой задачей, следующая не стартует, а после cancel() стартует; Run получает контекст своей задачи, и отмена одной задачи не трогает другую; три взаимно ждущие задачи завершаются только на трёх параллельных воркерах; workers = 0 и отрицательные значения дают одного воркера; Stop дожидается выполняющейся задачи, повторный Stop не паникует, горутин не остаётся.