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 { | ||||
| 	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
	 Violet Hynes
					Violet Hynes