Нужны WithCancel, проверка maxWorkers и ошибок создания запроса, отменяемое ожидание слота и возврат error. Сейчас cancel не определён, return отсутствует, а горутин создаётся по числу сообщений.
func (c *Client) Send(ctx context.Context, messages []Message, maxWorkers int) error {
Нужны WithCancel, проверка maxWorkers и ошибок создания запроса, отменяемое ожидание слота и возврат error. Сейчас cancel не определён, return отсутствует, а горутин создаётся по числу сообщений.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
func (c *Client) Send(ctx context.Context, messages []Message, maxWorkers int) error { sem := make(chan struct{}, maxWorkers)
var wg sync.WaitGroup
var resultErr error
var once sync.Once
for _, msg := range messages {
wg.Add(1)
go func(msg Message) {
defer wg.Done()
sem <- struct{}{}
defer func() {
<-sem
}()
body, err := json.Marshal(msg)
if err != nil {
once.Do(func() { resultErr = err; cancel() })
return
}
req, _ := http.NewRequestWithContext(
ctx,
http.MethodPost,
c.url,
bytes.NewReader(body),
)
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
once.Do(func() { resultErr = err; cancel()})
return
}
defer resp.Body.Close()
if resp.StatusCode >= 500 {
once.Do(func() {
resultErr = fmt.Errorf("server error")
cancel()})
return
}
c.sentCount.Add(1)
time.Sleep(time.Second)
}(msg)
}
wg.Wait()
}
Ответ
Нужны WithCancel, проверка maxWorkers и ошибок создания запроса, отменяемое ожидание слота и возврат error. Сейчас cancel не определён, return отсутствует, а горутин создаётся по числу сообщений.
Минимальная схема с ограничением числа активных горутин; успехом здесь считаются HTTP 2xx:
func (c *Client) Send(ctx context.Context, messages []Message, maxWorkers int) error {
if maxWorkers <= 0 { return fmt.Errorf("maxWorkers must be positive") }
ctx, cancel := context.WithCancel(ctx)
defer cancel()
sem := make(chan struct{}, maxWorkers)
var wg sync.WaitGroup
var once sync.Once
var resultErr error
fail := func(err error) {
once.Do(func() { resultErr = err; cancel() })
}
loop:
for _, msg := range messages {
select {
case <-ctx.Done(): break loop
case sem <- struct{}{}:
}
if ctx.Err() != nil { <-sem; break }
wg.Add(1)
go func(msg Message) {
defer wg.Done()
defer func() { <-sem }()
body, err := json.Marshal(msg)
if err != nil { fail(err); return }
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.url, bytes.NewReader(body))
if err != nil { fail(err); return }
req.Header.Set("Content-Type", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil { fail(err); return }
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fail(fmt.Errorf("unexpected HTTP status: %d", resp.StatusCode))
return
}
c.sentCount.Add(1)
}(msg)
}
wg.Wait()
if resultErr != nil { return resultErr }
return ctx.Err()
}
Используются bytes, context, encoding/json, fmt, net/http, sync. Client, Message и атомарный sentCount остаются из условия. Для реального клиента задайте timeout; конкурентность не заменяет rate limit. Sleep удалён: пауза в каждой горутине не задаёт общий лимит запросов в секунду.