-
Notifications
You must be signed in to change notification settings - Fork 4.5k
delegatingresolver: Stop calls into delegates once the parent resolver is closed. #8195
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
Changes from 11 commits
6e30ca4
870f9c7
cc96759
d62e1ce
0fea86b
9022284
e15d5a2
17ed57d
2e3d22a
3c7921f
557d47a
4cda2d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,15 +44,19 @@ var ( | |
// | ||
// It implements the [resolver.Resolver] interface. | ||
type delegatingResolver struct { | ||
target resolver.Target // parsed target URI to be resolved | ||
cc resolver.ClientConn // gRPC ClientConn | ||
targetResolver resolver.Resolver // resolver for the target URI, based on its scheme | ||
proxyResolver resolver.Resolver // resolver for the proxy URI; nil if no proxy is configured | ||
proxyURL *url.URL // proxy URL, derived from proxy environment and target | ||
target resolver.Target // parsed target URI to be resolved | ||
cc resolver.ClientConn // gRPC ClientConn | ||
proxyURL *url.URL // proxy URL, derived from proxy environment and target | ||
|
||
mu sync.Mutex // protects all the fields below | ||
targetResolverState *resolver.State // state of the target resolver | ||
proxyAddrs []resolver.Address // resolved proxy addresses; empty if no proxy is configured | ||
|
||
// childMu serializes calls into child resolvers. It also protects access to | ||
// the following fields. | ||
childMu sync.Mutex | ||
targetResolver resolver.Resolver // resolver for the target URI, based on its scheme | ||
proxyResolver resolver.Resolver // resolver for the proxy URI; nil if no proxy is configured | ||
} | ||
|
||
// nopResolver is a resolver that does nothing. | ||
|
@@ -93,6 +97,11 @@ func New(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOpti | |
r := &delegatingResolver{ | ||
target: target, | ||
cc: cc, | ||
// Child resolvers may send a state update as soon as they're built. | ||
// Initialize children with no-op resolvers. When both children are nil, | ||
// the delegatingResolver is considered closed. | ||
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. Related: do we not need to hold 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. Yes, we need to. Updates from one child can trigger ResolveNow calls in the other child. This also avoids the need to initialize children here. Added code to acquire the mutex and release it. |
||
targetResolver: nopResolver{}, | ||
proxyResolver: nopResolver{}, | ||
} | ||
|
||
var err error | ||
|
@@ -134,12 +143,6 @@ func New(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOpti | |
return nil, fmt.Errorf("delegating_resolver: failed to build resolver for proxy URL %q: %v", r.proxyURL, err) | ||
} | ||
|
||
if r.targetResolver == nil { | ||
r.targetResolver = nopResolver{} | ||
} | ||
if r.proxyResolver == nil { | ||
r.proxyResolver = nopResolver{} | ||
} | ||
return r, nil | ||
} | ||
|
||
|
@@ -165,11 +168,15 @@ func (r *delegatingResolver) proxyURIResolver(opts resolver.BuildOptions) (resol | |
} | ||
|
||
func (r *delegatingResolver) ResolveNow(o resolver.ResolveNowOptions) { | ||
r.childMu.Lock() | ||
defer r.childMu.Unlock() | ||
r.targetResolver.ResolveNow(o) | ||
r.proxyResolver.ResolveNow(o) | ||
} | ||
|
||
func (r *delegatingResolver) Close() { | ||
r.childMu.Lock() | ||
defer r.childMu.Unlock() | ||
r.targetResolver.Close() | ||
r.targetResolver = nil | ||
|
||
|
@@ -267,15 +274,27 @@ func (r *delegatingResolver) updateProxyResolverState(state resolver.State) erro | |
err := r.updateClientConnStateLocked() | ||
// Another possible approach was to block until updates are received from | ||
// both resolvers. But this is not used because calling `New()` triggers | ||
// `Build()` for the first resolver, which calls `UpdateState()`. And the | ||
// `Build()` for the first resolver, which calls `UpdateState()`. And the | ||
// second resolver hasn't sent an update yet, so it would cause `New()` to | ||
// block indefinitely. | ||
if err != nil { | ||
r.targetResolver.ResolveNow(resolver.ResolveNowOptions{}) | ||
go func() { | ||
r.childMu.Lock() | ||
defer r.childMu.Unlock() | ||
if !r.isClosedLocked() { | ||
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. Probably a nit, but IMO it is simpler to delete the helper and here just check if 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. Deleted the helper and added a check. |
||
r.targetResolver.ResolveNow(resolver.ResolveNowOptions{}) | ||
} | ||
}() | ||
} | ||
return err | ||
} | ||
|
||
// isClosedLocked returns true if the resolver is closed. Callers must hold | ||
// childMu. | ||
func (r *delegatingResolver) isClosedLocked() bool { | ||
return r.targetResolver == nil && r.proxyResolver == nil | ||
} | ||
|
||
// updateTargetResolverState updates the target resolver state by storing target | ||
// addresses, endpoints, and service config, marking the resolver as ready, and | ||
// triggering a state update if both resolvers are ready. If the ClientConn | ||
|
@@ -291,7 +310,13 @@ func (r *delegatingResolver) updateTargetResolverState(state resolver.State) err | |
r.targetResolverState = &state | ||
err := r.updateClientConnStateLocked() | ||
if err != nil { | ||
r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{}) | ||
go func() { | ||
r.childMu.Lock() | ||
defer r.childMu.Unlock() | ||
if !r.isClosedLocked() { | ||
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. As above. 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. Removed the helper function. |
||
r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{}) | ||
} | ||
}() | ||
} | ||
return nil | ||
} | ||
|
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.
Can we not share
mu
for this? Why not?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.
childMu
is being used to serialize calls into the child resolvers, similar to the serailzer queue in resolver_wrapper.go.If we re-use the existing mutex, we would need to hold
mu
while callingchildResolver.ResolveNow()
. If this callscc.UpdateState()
synchronously, it will try to re-acquiremu
and deadlock.mu
is used to serializeresolver -> channel
calls.childMu
is used to serializechannel -> resolver
calls.