Я отправляю HTTP-запросы из нескольких потоков по нескольким адресатам. Эти запросы требуют авторизации. У меня есть один сервер авторизации, откуда я получаю токен авторизации.
Итак, когда срок действия токена авторизации истекает (то есть статус ответа HTTP 401), я бы хотел, чтобы один из потоков go и refre sh токен, пока другие потоки ждут. Когда токен обновляется, все потоки должны продолжать отправлять запросы, не пытаясь повторно обновить sh токен.
Вот моя реализация:
type httpSenderAgent struct {
// irrelevant members omited
...
client *http.Client
tokenState int // (valid = 1, expired = 0)
token string
cond sync.Cond
}
func (a *httpSenderAgent) send(url string, body interface{}, retrycount int) error {
if retrycount >= MAX_AUTH_RETRY {
return errors.New("Max retry exceeded")
}
buf := &bytes.Buffer{}
json.NewEncoder(buf).Encode(body)
req, err := http.NewRequest("POST", url, buf)
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Authorization", "Bearer "+a.token)
res, err := a.client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == http.StatusUnauthorized {
a.refreshAuthToken()
return a.send(url , body, retrycount + 1)
}
if res.StatusCode < 200 || res.StatusCode > 299 {
return errors.New(http.StatusText(res.StatusCode))
}
return nil
}
func (a *httpSenderAgent) refreshAuthToken() {
// lock section 1
a.cond.L.Lock()
if a.tokenState == valid {
a.tokenState = expired
go func() {
// getAuthToken() is a http call to auth server
tkn := a.getAuthToken()
// lock section 2
a.cond.L.Lock()
a.token = tkn
a.tokenState = valid
a.cond.L.Unlock()
a.cond.Broadcast()
}()
}
for a.tokenState != valid {
a.cond.Wait()
}
a.cond.L.Unlock()
}
, но , проблема возникает в следующей ситуации:
Допустим, первый поток получает ответ 401, срок его действия истекает tokenState
, он запускает процедуру загрузки токена и ожидает, чтобы токен был действительным. Тем временем другой нить получает 401 и вызывает refreshAuthToken()
. К этому времени поток извлечения токенов входит в lock section 2
. Итак, второй поток не может ввести lock section 1
, он ожидает разблокировки мьютекса. Когда поток извлечения токенов обновляет tokenState
на действительный и разблокирует мьютекс, второй поток заблокирует мьютекс и обнаружит, что токен действителен. Итак, он будет повторять весь процесс извлечения токена.
Я проверил вопрос SO с таким же заголовком Java - разрешить одному потоку обновлять значение, другим ждать и пропускать критический раздел . Но ответы не охватывают мой вариант использования.
Итак, как я могу изменить это, чтобы получать токен аутентификации только один раз, пока не истечет снова? Какой примитив синхронизации я должен использовать для достижения этой цели?