mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 01:32:33 +00:00
Fix client clone with headers deadlock (#22410)
* Fix client clone with headers deadlock * Changelog * Typo
This commit is contained in:
@@ -998,7 +998,9 @@ func (c *Client) Namespace() string {
|
||||
func (c *Client) WithNamespace(namespace string) *Client {
|
||||
c2 := *c
|
||||
c2.modifyLock = sync.RWMutex{}
|
||||
c2.headers = c.Headers()
|
||||
c.modifyLock.RLock()
|
||||
c2.headers = c.headersInternal()
|
||||
c.modifyLock.RUnlock()
|
||||
if namespace == "" {
|
||||
c2.ClearNamespace()
|
||||
} else {
|
||||
@@ -1035,7 +1037,12 @@ func (c *Client) ClearToken() {
|
||||
func (c *Client) Headers() http.Header {
|
||||
c.modifyLock.RLock()
|
||||
defer c.modifyLock.RUnlock()
|
||||
return c.headersInternal()
|
||||
}
|
||||
|
||||
// headersInternal gets the current set of headers used for requests. Must be called
|
||||
// with the read modifyLock held.
|
||||
func (c *Client) headersInternal() http.Header {
|
||||
if c.headers == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1183,24 +1190,28 @@ func (c *Client) CloneTLSConfig() bool {
|
||||
// the api.Config struct, such as policy override and wrapping function
|
||||
// behavior, must currently then be set as desired on the new client.
|
||||
func (c *Client) Clone() (*Client, error) {
|
||||
c.modifyLock.RLock()
|
||||
defer c.modifyLock.RUnlock()
|
||||
c.config.modifyLock.RLock()
|
||||
defer c.config.modifyLock.RUnlock()
|
||||
return c.clone(c.config.CloneHeaders)
|
||||
}
|
||||
|
||||
// CloneWithHeaders creates a new client similar to Clone, with the difference
|
||||
// being that the headers are always cloned
|
||||
// being that the headers are always cloned
|
||||
func (c *Client) CloneWithHeaders() (*Client, error) {
|
||||
c.modifyLock.RLock()
|
||||
defer c.modifyLock.RUnlock()
|
||||
c.config.modifyLock.RLock()
|
||||
defer c.config.modifyLock.RUnlock()
|
||||
return c.clone(true)
|
||||
}
|
||||
|
||||
// clone creates a new client, with the headers being cloned based on the
|
||||
// passed in cloneheaders boolean
|
||||
// passed in cloneheaders boolean.
|
||||
// Must be called with the read lock and config read lock held.
|
||||
func (c *Client) clone(cloneHeaders bool) (*Client, error) {
|
||||
c.modifyLock.RLock()
|
||||
defer c.modifyLock.RUnlock()
|
||||
|
||||
config := c.config
|
||||
config.modifyLock.RLock()
|
||||
defer config.modifyLock.RUnlock()
|
||||
|
||||
newConfig := &Config{
|
||||
Address: config.Address,
|
||||
@@ -1230,7 +1241,7 @@ func (c *Client) clone(cloneHeaders bool) (*Client, error) {
|
||||
}
|
||||
|
||||
if cloneHeaders {
|
||||
client.SetHeaders(c.Headers().Clone())
|
||||
client.SetHeaders(c.headersInternal().Clone())
|
||||
}
|
||||
|
||||
if config.CloneToken {
|
||||
@@ -1261,6 +1272,7 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
|
||||
mfaCreds := c.mfaCreds
|
||||
wrappingLookupFunc := c.wrappingLookupFunc
|
||||
policyOverride := c.policyOverride
|
||||
headers := c.headersInternal()
|
||||
c.modifyLock.RUnlock()
|
||||
|
||||
host := addr.Host
|
||||
@@ -1305,7 +1317,7 @@ func (c *Client) NewRequest(method, requestPath string) *Request {
|
||||
req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath)
|
||||
}
|
||||
|
||||
req.Headers = c.Headers()
|
||||
req.Headers = headers
|
||||
req.PolicyOverride = policyOverride
|
||||
|
||||
return req
|
||||
|
||||
@@ -738,6 +738,61 @@ func TestClone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCloneWithHeadersNoDeadlock confirms that the cloning of the client doesn't cause
|
||||
// a deadlock.
|
||||
// Raised in https://github.com/hashicorp/vault/issues/22393 -- there was a
|
||||
// potential deadlock caused by running the problematicFunc() function in
|
||||
// multiple goroutines.
|
||||
func TestCloneWithHeadersNoDeadlock(t *testing.T) {
|
||||
client, err := NewClient(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
problematicFunc := func() {
|
||||
wg.Add(1)
|
||||
client.SetCloneToken(true)
|
||||
_, err := client.CloneWithHeaders()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
go problematicFunc()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TestCloneNoDeadlock is like TestCloneWithHeadersNoDeadlock but with
|
||||
// Clone instead of CloneWithHeaders
|
||||
func TestCloneNoDeadlock(t *testing.T) {
|
||||
client, err := NewClient(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
problematicFunc := func() {
|
||||
wg.Add(1)
|
||||
client.SetCloneToken(true)
|
||||
_, err := client.Clone()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
go problematicFunc()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSetHeadersRaceSafe(t *testing.T) {
|
||||
client, err1 := NewClient(nil)
|
||||
if err1 != nil {
|
||||
|
||||
3
changelog/22410.txt
Normal file
3
changelog/22410.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:bug
|
||||
api/client: Fix deadlock in client.CloneWithHeaders when used alongside other client methods.
|
||||
```
|
||||
Reference in New Issue
Block a user