Остановить программу при возникновении ошибки любого URL

Передайте запросам общий отменяемый context. Первая ошибка вызывает cancel, после чего функция ждёт завершения работников через WaitGroup.

Короткий ответ

Что ответить на собеседовании

Передайте запросам общий отменяемый 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, сообщает ошибку и возвращается. Отмена транспорта кооперативна, не мгновенна. Для больших списков дополнительно ограничивают параллелизм.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее