Передайте запросам общий отменяемый context. Первая ошибка вызывает cancel, после чего функция ждёт завершения работников через WaitGroup.
Остановить программу при возникновении ошибки любого URL
Передайте запросам общий отменяемый context. Первая ошибка вызывает cancel, после чего функция ждёт завершения работников через WaitGroup.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
Необходимо доработать существующее приложение так, чтобы при получении ошибки от хотя бы одного из запрошенных адресов все запущенные горутины прекращали свою работу, а программа завершалась.
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
func main() {
urls := []string{
"https://www.lamoda.ru",
"https://www.yandex.ru",
"https://www.mail.ru",
"https://www.google.com",
}
wg := &sync.WaitGroup{}
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
fmt.Printf("Fetching %s...\n", url)
err := fetchUrl(url)
if err != nil {
fmt.Printf("Error fetching %s: %v\n", url, err)
return
}
fmt.Printf("Fetched %s\n", url)
}(url)
}
wg.Wait()
fmt.Println("Program finished.")
}
func fetchUrl(url string) error {
// _, err := http.Get(url)
return nil
}
Ответ
Передайте запросам общий отменяемый context. Первая ошибка вызывает cancel, после чего функция ждёт завершения работников через WaitGroup.
В исходнике fetchUrl не делает запрос, а неиспользуемые http и time мешают компиляции. Реальная функция:
import (
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
)
func fetchAll(urls []string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
client := &http.Client{Timeout: 10 * time.Second}
var wg sync.WaitGroup
var once sync.Once
var firstErr error
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
err := func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { return err }
resp, err := client.Do(req)
if err != nil { return err }
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("HTTP status %d", resp.StatusCode)
}
_, err = io.Copy(io.Discard, resp.Body)
return err
}()
if err != nil {
once.Do(func() { firstErr = err; cancel() })
}
}(url)
}
wg.Wait()
return firstErr
}
main вызывает fetchAll, сообщает ошибку и возвращается. Отмена транспорта кооперативна, не мгновенна. Для больших списков дополнительно ограничивают параллелизм.