我正在从多个线程向多个目标发送HTTP请求。这些请求需要授权。我只有一个授权服务器,从那里可以获取授权令牌。
因此,当授权令牌到期时(即HTTP响应状态401),我希望其中一个线程去刷新令牌,而其他线程在等待。刷新令牌后,所有线程应继续发送请求,而无需再次刷新令牌。
这是我的实现方式
RAISEERROR
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
}
但是,在以下情况下会出现问题:
比方说,第一个线程获得401响应,它使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()
}
过期,启动goroutine来获取令牌,并等待令牌有效,与此同时,另一个线程获得401并调用{{1} }。此时,令牌提取线程进入tokenState
。因此,第二个线程无法输入refreshAuthToken()
,它等待互斥锁被解锁。当令牌获取线程将lock section 2
更新为有效并解锁互斥锁时,第二个线程将锁定该互斥锁并发现该令牌有效。因此,它将重复整个令牌获取过程。
我已经检出了标题为Java- Allow one thread to update a value,others to wait and skip critical section的SO问题。但是,答案并不涵盖我的用例。
那么,如何更改此方法以仅获取一次身份验证令牌,直到再次过期?我应该使用什么同步原语来实现此目的?