Skip to content

Commit 09ee0d4

Browse files
authored
Allow diagnostics endpoints command to receive more than one message (#13285)
The `linkerd diagnostics endpoints` command initiates a `Get` lookup to the destination controller to get the set of endpoints for a destination. This is a streaming response API and the command takes only the first response message and displays it. However, the full current state of endpoints may be split across multiple messages, resulting in an incomplete list of endpoints displayed. We instead read continuously from the response stream for a short amount of time (5 seconds) before displaying the full set of endpoints received. Signed-off-by: Alex Leong <[email protected]>
1 parent 600e96a commit 09ee0d4

File tree

1 file changed

+25
-19
lines changed

1 file changed

+25
-19
lines changed

cli/cmd/endpoints.go

+25-19
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9+
"io"
910
"os"
1011
"sort"
1112
"strings"
12-
"sync"
1313
"text/tabwriter"
14+
"time"
1415

1516
destinationPb "github.com/linkerd/linkerd2-proxy-api/go/destination"
1617
netPb "github.com/linkerd/linkerd2-proxy-api/go/net"
@@ -142,15 +143,11 @@ destination.`,
142143

143144
func requestEndpointsFromAPI(client destinationPb.DestinationClient, token string, authorities []string) (endpointsInfo, error) {
144145
info := make(endpointsInfo)
145-
// buffered channels to avoid blocking
146-
events := make(chan *destinationPb.Update, len(authorities))
147-
errs := make(chan error, len(authorities))
148-
var wg sync.WaitGroup
146+
events := make(chan *destinationPb.Update, 1000)
147+
errs := make(chan error, 1000)
149148

150149
for _, authority := range authorities {
151-
wg.Add(1)
152150
go func(authority string) {
153-
defer wg.Done()
154151
if len(errs) == 0 {
155152
dest := &destinationPb.GetDestination{
156153
Scheme: "http:",
@@ -164,22 +161,31 @@ func requestEndpointsFromAPI(client destinationPb.DestinationClient, token strin
164161
return
165162
}
166163

167-
event, err := rsp.Recv()
168-
if err != nil {
169-
if grpcError, ok := status.FromError(err); ok {
170-
err = errors.New(grpcError.Message())
164+
// Endpoint state may be sent in multiple messages so it's not
165+
// sufficient to read only the first message. Instead, we
166+
// continuously read from the stream. This goroutine will never
167+
// terminate if there are no errors, but this is okay for a
168+
// short lived CLI command.
169+
for {
170+
event, err := rsp.Recv()
171+
if errors.Is(err, io.EOF) {
172+
return
173+
} else if err != nil {
174+
if grpcError, ok := status.FromError(err); ok {
175+
err = errors.New(grpcError.Message())
176+
}
177+
errs <- err
178+
return
171179
}
172-
errs <- err
173-
return
180+
events <- event
174181
}
175-
events <- event
176182
}
177183
}(authority)
178184
}
179-
// Block till all goroutines above are done
180-
wg.Wait()
185+
// Wait an amount of time for some endpoint responses to be received.
186+
timeout := time.NewTimer(5 * time.Second)
181187

182-
for i := 0; i < len(authorities); i++ {
188+
for {
183189
select {
184190
case err := <-errs:
185191
// we only care about the first error
@@ -210,10 +216,10 @@ func requestEndpointsFromAPI(client destinationPb.DestinationClient, token strin
210216
http2: addr.GetHttp2(),
211217
})
212218
}
219+
case <-timeout.C:
220+
return info, nil
213221
}
214222
}
215-
216-
return info, nil
217223
}
218224

219225
func getIP(tcpAddr *netPb.TcpAddress) string {

0 commit comments

Comments
 (0)