-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #1165 by having threads wait for any outstanding connect to finish. #1170
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
40c5af6
Fixes #1165 by having threads wait for any outstanding connect to fin…
8bca3eb
Adds missing ref count for the race condition case.
072811f
Gets rid of follow up attempts if the lead thread can't connect.
1e8937b
Cleans up locking and factors markForUse into a Conn method.
614bf44
Changes to an unbuffered channel, since we just close it.
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,6 +114,12 @@ func (c *Conn) returnClient(client *StreamClient) { | |
} | ||
} | ||
|
||
// markForUse does all the bookkeeping required to ready a connection for use. | ||
func (c *Conn) markForUse() { | ||
c.lastUsed = time.Now() | ||
atomic.AddInt32(&c.refCount, 1) | ||
} | ||
|
||
// ConnPool is used to maintain a connection pool to other | ||
// Consul servers. This is used to reduce the latency of | ||
// RPC requests between servers. It is only used to pool | ||
|
@@ -134,6 +140,12 @@ type ConnPool struct { | |
// Pool maps an address to a open connection | ||
pool map[string]*Conn | ||
|
||
// limiter is used to throttle the number of connect attempts | ||
// to a given address. The first thread will attempt a connection | ||
// and put a channel in here, which all other threads will wait | ||
// on to close. | ||
limiter map[string]chan struct{} | ||
|
||
// TLS wrapper | ||
tlsWrap tlsutil.DCWrapper | ||
|
||
|
@@ -153,6 +165,7 @@ func NewPool(logOutput io.Writer, maxTime time.Duration, maxStreams int, tlsWrap | |
maxTime: maxTime, | ||
maxStreams: maxStreams, | ||
pool: make(map[string]*Conn), | ||
limiter: make(map[string]chan struct{}), | ||
tlsWrap: tlsWrap, | ||
shutdownCh: make(chan struct{}), | ||
} | ||
|
@@ -180,28 +193,69 @@ func (p *ConnPool) Shutdown() error { | |
return nil | ||
} | ||
|
||
// Acquire is used to get a connection that is | ||
// pooled or to return a new connection | ||
// acquire will return a pooled connection, if available. Otherwise it will | ||
// wait for an existing connection attempt to finish, if one if in progress, | ||
// and will return that one if it succeeds. If all else fails, it will return a | ||
// newly-created connection and add it to the pool. | ||
func (p *ConnPool) acquire(dc string, addr net.Addr, version int) (*Conn, error) { | ||
// Check for a pooled ocnn | ||
if conn := p.getPooled(addr, version); conn != nil { | ||
return conn, nil | ||
// Check to see if there's a pooled connection available. This is up | ||
// here since it should the the vastly more common case than the rest | ||
// of the code here. | ||
p.Lock() | ||
c := p.pool[addr.String()] | ||
if c != nil { | ||
c.markForUse() | ||
p.Unlock() | ||
return c, nil | ||
} | ||
|
||
// Create a new connection | ||
return p.getNewConn(dc, addr, version) | ||
} | ||
// If not (while we are still locked), set up the throttling structure | ||
// for this address, which will make everyone else wait until our | ||
// attempt is done. | ||
var wait chan struct{} | ||
var ok bool | ||
if wait, ok = p.limiter[addr.String()]; !ok { | ||
wait = make(chan struct{}, 1) | ||
p.limiter[addr.String()] = wait | ||
} | ||
isLeadThread := !ok | ||
p.Unlock() | ||
|
||
// If we are the lead thread, make the new connection and then wake | ||
// everybody else up to see if we got it. | ||
if isLeadThread { | ||
c, err := p.getNewConn(dc, addr, version) | ||
p.Lock() | ||
delete(p.limiter, addr.String()) | ||
close(wait) | ||
if err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Super minor, but can we move the code in the defer to after |
||
p.Unlock() | ||
return nil, err | ||
} | ||
|
||
p.pool[addr.String()] = c | ||
p.Unlock() | ||
return c, nil | ||
} | ||
|
||
// Otherwise, wait for the lead thread to attempt the connection | ||
// and use what's in the pool at that point. | ||
select { | ||
case <-p.shutdownCh: | ||
return nil, fmt.Errorf("rpc error: shutdown") | ||
case <-wait: | ||
} | ||
|
||
// getPooled is used to return a pooled connection | ||
func (p *ConnPool) getPooled(addr net.Addr, version int) *Conn { | ||
// See if the lead thread was able to get us a connection. | ||
p.Lock() | ||
c := p.pool[addr.String()] | ||
if c != nil { | ||
c.lastUsed = time.Now() | ||
atomic.AddInt32(&c.refCount, 1) | ||
if c := p.pool[addr.String()]; c != nil { | ||
c.markForUse() | ||
p.Unlock() | ||
return c, nil | ||
} | ||
|
||
p.Unlock() | ||
return c | ||
return nil, fmt.Errorf("rpc error: lead thread didn't get connection") | ||
} | ||
|
||
// getNewConn is used to return a new connection | ||
|
@@ -272,18 +326,7 @@ func (p *ConnPool) getNewConn(dc string, addr net.Addr, version int) (*Conn, err | |
version: version, | ||
pool: p, | ||
} | ||
|
||
// Track this connection, handle potential race condition | ||
p.Lock() | ||
if existing := p.pool[addr.String()]; existing != nil { | ||
c.Close() | ||
p.Unlock() | ||
return existing, nil | ||
} else { | ||
p.pool[addr.String()] = c | ||
p.Unlock() | ||
return c, nil | ||
} | ||
return c, nil | ||
} | ||
|
||
// clearConn is used to clear any cached connection, potentially in response to an erro | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't need to buffer it, since we just close