mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +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 { | func (c *Client) WithNamespace(namespace string) *Client { | ||||||
| 	c2 := *c | 	c2 := *c | ||||||
| 	c2.modifyLock = sync.RWMutex{} | 	c2.modifyLock = sync.RWMutex{} | ||||||
| 	c2.headers = c.Headers() | 	c.modifyLock.RLock() | ||||||
|  | 	c2.headers = c.headersInternal() | ||||||
|  | 	c.modifyLock.RUnlock() | ||||||
| 	if namespace == "" { | 	if namespace == "" { | ||||||
| 		c2.ClearNamespace() | 		c2.ClearNamespace() | ||||||
| 	} else { | 	} else { | ||||||
| @@ -1035,7 +1037,12 @@ func (c *Client) ClearToken() { | |||||||
| func (c *Client) Headers() http.Header { | func (c *Client) Headers() http.Header { | ||||||
| 	c.modifyLock.RLock() | 	c.modifyLock.RLock() | ||||||
| 	defer c.modifyLock.RUnlock() | 	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 { | 	if c.headers == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| @@ -1183,24 +1190,28 @@ func (c *Client) CloneTLSConfig() bool { | |||||||
| // the api.Config struct, such as policy override and wrapping function | // the api.Config struct, such as policy override and wrapping function | ||||||
| // behavior, must currently then be set as desired on the new client. | // behavior, must currently then be set as desired on the new client. | ||||||
| func (c *Client) Clone() (*Client, error) { | 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) | 	return c.clone(c.config.CloneHeaders) | ||||||
| } | } | ||||||
|  |  | ||||||
| // CloneWithHeaders creates a new client similar to Clone, with the difference | // 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) { | 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) | 	return c.clone(true) | ||||||
| } | } | ||||||
|  |  | ||||||
| // clone creates a new client, with the headers being cloned based on the | // 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) { | func (c *Client) clone(cloneHeaders bool) (*Client, error) { | ||||||
| 	c.modifyLock.RLock() |  | ||||||
| 	defer c.modifyLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	config := c.config | 	config := c.config | ||||||
| 	config.modifyLock.RLock() |  | ||||||
| 	defer config.modifyLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	newConfig := &Config{ | 	newConfig := &Config{ | ||||||
| 		Address:        config.Address, | 		Address:        config.Address, | ||||||
| @@ -1230,7 +1241,7 @@ func (c *Client) clone(cloneHeaders bool) (*Client, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if cloneHeaders { | 	if cloneHeaders { | ||||||
| 		client.SetHeaders(c.Headers().Clone()) | 		client.SetHeaders(c.headersInternal().Clone()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if config.CloneToken { | 	if config.CloneToken { | ||||||
| @@ -1261,6 +1272,7 @@ func (c *Client) NewRequest(method, requestPath string) *Request { | |||||||
| 	mfaCreds := c.mfaCreds | 	mfaCreds := c.mfaCreds | ||||||
| 	wrappingLookupFunc := c.wrappingLookupFunc | 	wrappingLookupFunc := c.wrappingLookupFunc | ||||||
| 	policyOverride := c.policyOverride | 	policyOverride := c.policyOverride | ||||||
|  | 	headers := c.headersInternal() | ||||||
| 	c.modifyLock.RUnlock() | 	c.modifyLock.RUnlock() | ||||||
|  |  | ||||||
| 	host := addr.Host | 	host := addr.Host | ||||||
| @@ -1305,7 +1317,7 @@ func (c *Client) NewRequest(method, requestPath string) *Request { | |||||||
| 		req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath) | 		req.WrapTTL = DefaultWrappingLookupFunc(method, lookupPath) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	req.Headers = c.Headers() | 	req.Headers = headers | ||||||
| 	req.PolicyOverride = policyOverride | 	req.PolicyOverride = policyOverride | ||||||
|  |  | ||||||
| 	return req | 	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) { | func TestSetHeadersRaceSafe(t *testing.T) { | ||||||
| 	client, err1 := NewClient(nil) | 	client, err1 := NewClient(nil) | ||||||
| 	if err1 != 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
	 Violet Hynes
					Violet Hynes