Multithreading 允许一个线程更新值(刷新身份验证令牌),其他线程等待并跳过关键部分
我正在从多个线程向多个目标发送HTTP请求。这些请求需要授权。我有一个单一的授权服务器,从那里我可以获得授权令牌 因此,当授权令牌到期时(即HTTP响应状态401),我希望其中一个线程在其他线程等待时去刷新令牌。刷新令牌后,所有线程应继续发送请求,而无需再次尝试刷新令牌 以下是我的实现:Multithreading 允许一个线程更新值(刷新身份验证令牌),其他线程等待并跳过关键部分,multithreading,go,mutex,condition-variable,Multithreading,Go,Mutex,Condition Variable,我正在从多个线程向多个目标发送HTTP请求。这些请求需要授权。我有一个单一的授权服务器,从那里我可以获得授权令牌 因此,当授权令牌到期时(即HTTP响应状态401),我希望其中一个线程在其他线程等待时去刷新令牌。刷新令牌后,所有线程应继续发送请求,而无需再次尝试刷新令牌 以下是我的实现: type httpSenderAgent struct { // irrelevant members omited ... client *http.Client tokenS
type httpSenderAgent struct {
// irrelevant members omited
...
client *http.Client
tokenState int // (valid = 1, expired = 0)
token string
cond sync.Cond
}
但是,问题出现在以下情况:
假设第一个线程得到401响应,它使tokenState
过期,启动goroutine获取令牌并等待令牌有效,同时,另一个线程得到401并调用refreshAuthToken()
。此时,令牌获取线程进入锁定节2
。因此,第二个线程无法进入lock section 1
,它等待互斥锁解锁。当令牌获取线程将tokenState
更新为valid并解锁互斥锁时,第二个线程将锁定互斥锁并发现令牌有效。因此,它将重复整个令牌获取过程
我已经用相同的标题检查了SO问题。但是,答案并不包括我的用例
那么,我如何更改它以仅获取一次身份验证令牌,直到再次过期?我应该使用什么样的同步原语来实现这一点?我不确定您如何设想在这里使用
sync.Cond
,但听起来您正在寻找一些可以处理的方法,您是否有办法知道它何时过期,或者便宜地检查它是否过期?这将大大提高解决方案的复杂性。@jimbsingleflight似乎很有趣,我将尝试一下。但是,同步forget()
函数调用可能会导致相同的情况。除了sync.Cond
在这种情况下,您会建议使用哪种同步原语?@Adrian可以利用到期时间。但是,情况可能并非总是如此(当令牌被加密时)。我在处理MS的Graph API时使用了类似的技术。身份验证令牌为一小时(在身份验证响应中指示)。客户端api获取受互斥体保护的令牌来执行其工作。服务go例程在到期前5分钟执行重新身份验证-完成后在新令牌中交换互斥。客户端api在这些重新授权窗口期间不会出现服务中断。我不确定您如何设想一个sync.Cond
在这里工作,但听起来好像您正在寻找一些可以处理的方法。您是否有办法知道它何时过期,或者便宜地检查它是否过期?这将大大提高解决方案的复杂性。@jimbsingleflight似乎很有趣,我将尝试一下。但是,同步forget()
函数调用可能会导致相同的情况。除了sync.Cond
在这种情况下,您会建议使用哪种同步原语?@Adrian可以利用到期时间。但是,情况可能并非总是如此(当令牌被加密时)。我在处理MS的Graph API时使用了类似的技术。身份验证令牌为一小时(在身份验证响应中指示)。客户端api获取受互斥体保护的令牌来执行其工作。服务go例程在到期前5分钟执行重新身份验证-完成后在新令牌中交换互斥。在这些重新授权窗口期间,客户端api未发现服务中断。
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()
}