Skip to content

Commit 3a93562

Browse files
authored
Cherry-pick #8195 into v1.71.x (#8202)
1 parent 208e03b commit 3a93562

File tree

2 files changed

+244
-8
lines changed

2 files changed

+244
-8
lines changed

internal/resolver/delegatingresolver/delegatingresolver.go

+32-8
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,19 @@ var (
4444
//
4545
// It implements the [resolver.Resolver] interface.
4646
type delegatingResolver struct {
47-
target resolver.Target // parsed target URI to be resolved
48-
cc resolver.ClientConn // gRPC ClientConn
49-
targetResolver resolver.Resolver // resolver for the target URI, based on its scheme
50-
proxyResolver resolver.Resolver // resolver for the proxy URI; nil if no proxy is configured
51-
proxyURL *url.URL // proxy URL, derived from proxy environment and target
47+
target resolver.Target // parsed target URI to be resolved
48+
cc resolver.ClientConn // gRPC ClientConn
49+
proxyURL *url.URL // proxy URL, derived from proxy environment and target
5250

5351
mu sync.Mutex // protects all the fields below
5452
targetResolverState *resolver.State // state of the target resolver
5553
proxyAddrs []resolver.Address // resolved proxy addresses; empty if no proxy is configured
54+
55+
// childMu serializes calls into child resolvers. It also protects access to
56+
// the following fields.
57+
childMu sync.Mutex
58+
targetResolver resolver.Resolver // resolver for the target URI, based on its scheme
59+
proxyResolver resolver.Resolver // resolver for the proxy URI; nil if no proxy is configured
5660
}
5761

5862
// nopResolver is a resolver that does nothing.
@@ -111,6 +115,10 @@ func New(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOpti
111115
logger.Infof("Proxy URL detected : %s", r.proxyURL)
112116
}
113117

118+
// Resolver updates from one child may trigger calls into the other. Block
119+
// updates until the children are initialized.
120+
r.childMu.Lock()
121+
defer r.childMu.Unlock()
114122
// When the scheme is 'dns' and target resolution on client is not enabled,
115123
// resolution should be handled by the proxy, not the client. Therefore, we
116124
// bypass the target resolver and store the unresolved target address.
@@ -165,11 +173,15 @@ func (r *delegatingResolver) proxyURIResolver(opts resolver.BuildOptions) (resol
165173
}
166174

167175
func (r *delegatingResolver) ResolveNow(o resolver.ResolveNowOptions) {
176+
r.childMu.Lock()
177+
defer r.childMu.Unlock()
168178
r.targetResolver.ResolveNow(o)
169179
r.proxyResolver.ResolveNow(o)
170180
}
171181

172182
func (r *delegatingResolver) Close() {
183+
r.childMu.Lock()
184+
defer r.childMu.Unlock()
173185
r.targetResolver.Close()
174186
r.targetResolver = nil
175187

@@ -267,11 +279,17 @@ func (r *delegatingResolver) updateProxyResolverState(state resolver.State) erro
267279
err := r.updateClientConnStateLocked()
268280
// Another possible approach was to block until updates are received from
269281
// both resolvers. But this is not used because calling `New()` triggers
270-
// `Build()` for the first resolver, which calls `UpdateState()`. And the
282+
// `Build()` for the first resolver, which calls `UpdateState()`. And the
271283
// second resolver hasn't sent an update yet, so it would cause `New()` to
272284
// block indefinitely.
273285
if err != nil {
274-
r.targetResolver.ResolveNow(resolver.ResolveNowOptions{})
286+
go func() {
287+
r.childMu.Lock()
288+
defer r.childMu.Unlock()
289+
if r.targetResolver != nil {
290+
r.targetResolver.ResolveNow(resolver.ResolveNowOptions{})
291+
}
292+
}()
275293
}
276294
return err
277295
}
@@ -291,7 +309,13 @@ func (r *delegatingResolver) updateTargetResolverState(state resolver.State) err
291309
r.targetResolverState = &state
292310
err := r.updateClientConnStateLocked()
293311
if err != nil {
294-
r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{})
312+
go func() {
313+
r.childMu.Lock()
314+
defer r.childMu.Unlock()
315+
if r.proxyResolver != nil {
316+
r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{})
317+
}
318+
}()
295319
}
296320
return nil
297321
}

internal/resolver/delegatingresolver/delegatingresolver_ext_test.go

+212
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package delegatingresolver_test
2020

2121
import (
22+
"context"
23+
"errors"
2224
"net/http"
2325
"net/url"
2426
"testing"
@@ -548,3 +550,213 @@ func (s) TestDelegatingResolverForMutipleProxyAddress(t *testing.T) {
548550
t.Fatalf("Unexpected state from delegating resolver. Diff (-got +want):\n%v", diff)
549551
}
550552
}
553+
554+
// Tests that delegatingresolver doesn't panic when the channel closes the
555+
// resolver while it's handling an update from it's child. The test closes the
556+
// delegating resolver, verifies the target resolver is closed and blocks the
557+
// proxy resolver from being closed. The test sends an update from the proxy
558+
// resolver and verifies that the target resolver's ResolveNow method is not
559+
// called after the channels returns an error.
560+
func (s) TestDelegatingResolverUpdateStateDuringClose(t *testing.T) {
561+
const envProxyAddr = "proxytest.com"
562+
563+
hpfe := func(req *http.Request) (*url.URL, error) {
564+
return &url.URL{
565+
Scheme: "https",
566+
Host: envProxyAddr,
567+
}, nil
568+
}
569+
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
570+
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
571+
defer func() {
572+
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
573+
}()
574+
575+
// Manual resolver to control the target resolution.
576+
targetResolver := manual.NewBuilderWithScheme("test")
577+
targetResolverCalled := make(chan struct{})
578+
targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
579+
close(targetResolverCalled)
580+
}
581+
targetResolverCloseCalled := make(chan struct{})
582+
targetResolver.CloseCallback = func() {
583+
close(targetResolverCloseCalled)
584+
t.Log("Target resolver is closed.")
585+
}
586+
587+
target := targetResolver.Scheme() + ":///" + "ignored"
588+
// Set up a manual DNS resolver to control the proxy address resolution.
589+
proxyResolver := setupDNS(t)
590+
591+
unblockProxyResolverClose := make(chan struct{})
592+
proxyResolver.CloseCallback = func() {
593+
<-unblockProxyResolverClose
594+
t.Log("Proxy resolver is closed.")
595+
}
596+
597+
tcc, _, _ := createTestResolverClientConn(t)
598+
tcc.UpdateStateF = func(resolver.State) error {
599+
return errors.New("test error")
600+
}
601+
dr, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
602+
if err != nil {
603+
t.Fatalf("Failed to create delegating resolver: %v", err)
604+
}
605+
606+
targetResolver.UpdateState(resolver.State{
607+
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
608+
})
609+
610+
// Closing the delegating resolver will block until the test writes to the
611+
// unblockProxyResolverClose channel.
612+
go dr.Close()
613+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
614+
defer cancel()
615+
select {
616+
case <-targetResolverCloseCalled:
617+
case <-ctx.Done():
618+
t.Fatalf("Context timed out waiting for target resolver's Close method to be called.")
619+
}
620+
621+
// Updating the channel will result in an error being returned. Since the
622+
// target resolver's Close method is already called, the delegating resolver
623+
// must not call "ResolveNow" on it.
624+
go proxyResolver.UpdateState(resolver.State{
625+
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
626+
})
627+
unblockProxyResolverClose <- struct{}{}
628+
629+
select {
630+
case <-targetResolverCalled:
631+
t.Fatalf("targetResolver.ResolveNow() called unexpectedly.")
632+
case <-time.After(defaultTestShortTimeout):
633+
}
634+
}
635+
636+
// Tests that calling cc.UpdateState in a blocking manner from a child resolver
637+
// while handling a ResolveNow call doesn't result in a deadlock. The test uses
638+
// a fake ClientConn that returns an error when calling cc.UpdateState. The test
639+
// makes the proxy resolver update the resolver state. The test verifies that
640+
// the delegating resolver calls ResolveNow on the target resolver when the
641+
// ClientConn returns an error.
642+
func (s) TestDelegatingResolverUpdateStateFromResolveNow(t *testing.T) {
643+
const envProxyAddr = "proxytest.com"
644+
645+
hpfe := func(req *http.Request) (*url.URL, error) {
646+
return &url.URL{
647+
Scheme: "https",
648+
Host: envProxyAddr,
649+
}, nil
650+
}
651+
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
652+
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
653+
defer func() {
654+
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
655+
}()
656+
657+
// Manual resolver to control the target resolution.
658+
targetResolver := manual.NewBuilderWithScheme("test")
659+
targetResolverCalled := make(chan struct{})
660+
targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
661+
// Updating the resolver state should not deadlock.
662+
targetResolver.CC.UpdateState(resolver.State{
663+
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
664+
})
665+
close(targetResolverCalled)
666+
}
667+
668+
target := targetResolver.Scheme() + ":///" + "ignored"
669+
// Set up a manual DNS resolver to control the proxy address resolution.
670+
proxyResolver := setupDNS(t)
671+
672+
tcc, _, _ := createTestResolverClientConn(t)
673+
tcc.UpdateStateF = func(resolver.State) error {
674+
return errors.New("test error")
675+
}
676+
_, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
677+
if err != nil {
678+
t.Fatalf("Failed to create delegating resolver: %v", err)
679+
}
680+
681+
targetResolver.UpdateState(resolver.State{
682+
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
683+
})
684+
685+
// Updating the channel will result in an error being returned. The
686+
// delegating resolver should call call "ResolveNow" on the target resolver.
687+
proxyResolver.UpdateState(resolver.State{
688+
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
689+
})
690+
691+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
692+
defer cancel()
693+
select {
694+
case <-targetResolverCalled:
695+
case <-ctx.Done():
696+
t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
697+
}
698+
}
699+
700+
// Tests that calling cc.UpdateState in a blocking manner from child resolvers
701+
// doesn't result in deadlocks.
702+
func (s) TestDelegatingResolverResolveNow(t *testing.T) {
703+
const envProxyAddr = "proxytest.com"
704+
705+
hpfe := func(req *http.Request) (*url.URL, error) {
706+
return &url.URL{
707+
Scheme: "https",
708+
Host: envProxyAddr,
709+
}, nil
710+
}
711+
originalhpfe := delegatingresolver.HTTPSProxyFromEnvironment
712+
delegatingresolver.HTTPSProxyFromEnvironment = hpfe
713+
defer func() {
714+
delegatingresolver.HTTPSProxyFromEnvironment = originalhpfe
715+
}()
716+
717+
// Manual resolver to control the target resolution.
718+
targetResolver := manual.NewBuilderWithScheme("test")
719+
targetResolverCalled := make(chan struct{})
720+
targetResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
721+
// Updating the resolver state should not deadlock.
722+
targetResolver.CC.UpdateState(resolver.State{
723+
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
724+
})
725+
close(targetResolverCalled)
726+
}
727+
728+
target := targetResolver.Scheme() + ":///" + "ignored"
729+
// Set up a manual DNS resolver to control the proxy address resolution.
730+
proxyResolver := setupDNS(t)
731+
proxyResolverCalled := make(chan struct{})
732+
proxyResolver.ResolveNowCallback = func(resolver.ResolveNowOptions) {
733+
// Updating the resolver state should not deadlock.
734+
proxyResolver.CC.UpdateState(resolver.State{
735+
Endpoints: []resolver.Endpoint{{Addresses: []resolver.Address{{Addr: "1.1.1.1"}}}},
736+
})
737+
close(proxyResolverCalled)
738+
}
739+
740+
tcc, _, _ := createTestResolverClientConn(t)
741+
dr, err := delegatingresolver.New(resolver.Target{URL: *testutils.MustParseURL(target)}, tcc, resolver.BuildOptions{}, targetResolver, false)
742+
if err != nil {
743+
t.Fatalf("Failed to create delegating resolver: %v", err)
744+
}
745+
746+
// Call ResolveNow on the delegatingResolver and verify both children
747+
// receive the ResolveNow call.
748+
dr.ResolveNow(resolver.ResolveNowOptions{})
749+
750+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
751+
defer cancel()
752+
select {
753+
case <-targetResolverCalled:
754+
case <-ctx.Done():
755+
t.Fatalf("context timed out waiting for targetResolver.ResolveNow() to be called.")
756+
}
757+
select {
758+
case <-proxyResolverCalled:
759+
case <-ctx.Done():
760+
t.Fatalf("context timed out waiting for proxyResolver.ResolveNow() to be called.")
761+
}
762+
}

0 commit comments

Comments
 (0)