@@ -2,12 +2,14 @@ package upstream
2
2
3
3
import (
4
4
"context"
5
+ "fmt"
5
6
"net/netip"
6
7
"time"
7
8
8
9
"github.com/AdguardTeam/golibs/errors"
9
10
"github.com/AdguardTeam/golibs/log"
10
11
"github.com/miekg/dns"
12
+ "golang.org/x/exp/slices"
11
13
)
12
14
13
15
const (
@@ -21,37 +23,40 @@ const (
21
23
22
24
// ExchangeParallel returns the dirst successful response from one of u. It
23
25
// returns an error if all upstreams failed to exchange the request.
24
- func ExchangeParallel (u []Upstream , req * dns.Msg ) (reply * dns.Msg , resolved Upstream , err error ) {
25
- upsNum := len (u )
26
+ func ExchangeParallel (ups []Upstream , req * dns.Msg ) (reply * dns.Msg , resolved Upstream , err error ) {
27
+ upsNum := len (ups )
26
28
switch upsNum {
27
29
case 0 :
28
30
return nil , nil , ErrNoUpstreams
29
31
case 1 :
30
- reply , err = exchangeAndLog (u [0 ], req )
32
+ reply , err = exchangeAndLog (ups [0 ], req )
31
33
32
- return reply , u [0 ], err
34
+ return reply , ups [0 ], err
33
35
default :
34
36
// Go on.
35
37
}
36
38
37
- resCh := make (chan * ExchangeAllResult )
38
- errCh := make (chan error )
39
- for _ , f := range u {
40
- go exchangeAsync (f , req , resCh , errCh )
39
+ resCh := make (chan any , upsNum )
40
+ for _ , f := range ups {
41
+ go exchangeAsync (f , req , resCh )
41
42
}
42
43
43
44
errs := []error {}
44
- for range u {
45
- select {
46
- case excErr := <- errCh :
47
- errs = append (errs , excErr )
48
- case rep := <- resCh :
49
- if rep .Resp != nil {
50
- return rep .Resp , rep .Upstream , nil
45
+ for range ups {
46
+ var r * ExchangeAllResult
47
+ r , err = receiveAsyncResult (resCh )
48
+ if err != nil {
49
+ if ! errors .Is (err , ErrNoReply ) {
50
+ errs = append (errs , err )
51
51
}
52
+ } else {
53
+ return r .Resp , r .Upstream , nil
52
54
}
53
55
}
54
56
57
+ // TODO(e.burkov): Probably it's better to return the joined error from
58
+ // each upstream that returned no response, and get rid of multiple
59
+ // [errors.Is] calls. This will change the behavior though.
55
60
if len (errs ) == 0 {
56
61
return nil , nil , errors .Error ("none of upstream servers responded" )
57
62
}
@@ -72,8 +77,8 @@ type ExchangeAllResult struct {
72
77
// ExchangeAll returns the responses from all of u. It returns an error only if
73
78
// all upstreams failed to exchange the request.
74
79
func ExchangeAll (ups []Upstream , req * dns.Msg ) (res []ExchangeAllResult , err error ) {
75
- upsl := len (ups )
76
- switch upsl {
80
+ upsNum := len (ups )
81
+ switch upsNum {
77
82
case 0 :
78
83
return nil , ErrNoUpstreams
79
84
case 1 :
@@ -90,62 +95,60 @@ func ExchangeAll(ups []Upstream, req *dns.Msg) (res []ExchangeAllResult, err err
90
95
// Go on.
91
96
}
92
97
93
- res = make ([]ExchangeAllResult , 0 , upsl )
98
+ res = make ([]ExchangeAllResult , 0 , upsNum )
94
99
var errs []error
95
100
96
- resCh := make (chan * ExchangeAllResult )
97
- errCh := make (chan error )
101
+ resCh := make (chan any , upsNum )
98
102
99
103
// Start exchanging concurrently.
100
104
for _ , u := range ups {
101
- go exchangeAsync (u , req , resCh , errCh )
105
+ go exchangeAsync (u , req , resCh )
102
106
}
103
107
104
108
// Wait for all exchanges to finish.
105
109
for range ups {
106
110
var r * ExchangeAllResult
107
- r , err = receiveAsyncResult (resCh , errCh )
111
+ r , err = receiveAsyncResult (resCh )
108
112
if err != nil {
109
113
errs = append (errs , err )
110
114
} else {
111
115
res = append (res , * r )
112
116
}
113
117
}
114
118
115
- if len (errs ) == upsl {
119
+ if len (errs ) == upsNum {
116
120
// TODO(e.burkov): Use [errors.Join] in Go 1.20.
117
121
return res , errors .List ("all upstreams failed to exchange" , errs ... )
118
122
}
119
123
120
- return res , nil
124
+ return slices . Clip ( res ) , nil
121
125
}
122
126
123
127
// receiveAsyncResult receives a single result from resCh or an error from
124
128
// errCh. It returns either a non-nil result or an error.
125
- func receiveAsyncResult (
126
- resCh chan * ExchangeAllResult ,
127
- errCh chan error ,
128
- ) (res * ExchangeAllResult , err error ) {
129
- select {
130
- case err = <- errCh :
131
- return nil , err
132
- case rep := <- resCh :
133
- if rep .Resp == nil {
129
+ func receiveAsyncResult (resCh chan any ) (res * ExchangeAllResult , err error ) {
130
+ switch res := (<- resCh ).(type ) {
131
+ case error :
132
+ return nil , res
133
+ case * ExchangeAllResult :
134
+ if res .Resp == nil {
134
135
return nil , ErrNoReply
135
136
}
136
137
137
- return rep , nil
138
+ return res , nil
139
+ default :
140
+ return nil , fmt .Errorf ("unexpected type %T of result" , res )
138
141
}
139
142
}
140
143
141
144
// exchangeAsync tries to resolve DNS request with one upstream and sends the
142
145
// result to respCh.
143
- func exchangeAsync (u Upstream , req * dns.Msg , respCh chan * ExchangeAllResult , errCh chan error ) {
146
+ func exchangeAsync (u Upstream , req * dns.Msg , resCh chan any ) {
144
147
reply , err := exchangeAndLog (u , req )
145
148
if err != nil {
146
- errCh <- err
149
+ resCh <- err
147
150
} else {
148
- respCh <- & ExchangeAllResult {Resp : reply , Upstream : u }
151
+ resCh <- & ExchangeAllResult {Resp : reply , Upstream : u }
149
152
}
150
153
}
151
154
@@ -156,12 +159,14 @@ func exchangeAndLog(u Upstream, req *dns.Msg) (resp *dns.Msg, err error) {
156
159
157
160
start := time .Now ()
158
161
reply , err := u .Exchange (req )
159
- elapsed := time .Since (start )
162
+ dur := time .Since (start )
160
163
161
- if q := & req .Question [0 ]; err == nil {
162
- log .Debug ("dnsproxy: upstream %s exchanged %s successfully in %s" , addr , q , elapsed )
163
- } else {
164
- log .Debug ("dnsproxy: upstream %s failed to exchange %s in %s: %s" , addr , q , elapsed , err )
164
+ if len (req .Question ) > 0 {
165
+ if q := & req .Question [0 ]; err == nil {
166
+ log .Debug ("dnsproxy: upstream %s exchanged %s successfully in %s" , addr , q , dur )
167
+ } else {
168
+ log .Debug ("dnsproxy: upstream %s failed to exchange %s in %s: %s" , addr , q , dur , err )
169
+ }
165
170
}
166
171
167
172
return reply , err
0 commit comments