github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/closedts/transport/clients.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package transport 12 13 import ( 14 "context" 15 "unsafe" 16 17 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts" 18 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/closedts/ctpb" 19 "github.com/cockroachdb/cockroach/pkg/roachpb" 20 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 21 "github.com/cockroachdb/cockroach/pkg/util/log" 22 "github.com/cockroachdb/cockroach/pkg/util/stop" 23 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 24 "github.com/cockroachdb/logtags" 25 ) 26 27 // Config holds the information necessary to create a client registry. 28 type Config struct { 29 Settings *cluster.Settings 30 Stopper *stop.Stopper 31 NodeID roachpb.NodeID 32 Dialer closedts.Dialer 33 Sink closedts.Notifyee 34 } 35 36 // Clients manages clients receiving closed timestamp updates from 37 // peer nodes, along with facilities to request information about certain 38 // ranges. Received updates are relayed to a provided Notifyee. 39 type Clients struct { 40 cfg Config 41 42 // TODO(tschottdorf): remove unused clients. Perhaps expiring them after, 43 // say, 24h is enough? There is no interruption when doing so; the only 44 // price is that a full update is sent, but that is pretty cheap too. 45 clients syncutil.IntMap 46 } 47 48 var _ closedts.ClientRegistry = (*Clients)(nil) 49 50 // NewClients sets up a client registry. 51 func NewClients(cfg Config) *Clients { 52 return &Clients{cfg: cfg} 53 } 54 55 type client struct { 56 mu struct { 57 syncutil.Mutex 58 requested map[roachpb.RangeID]struct{} // never nil 59 } 60 } 61 62 // Request is called when serving a follower read has failed due to missing or 63 // insufficient information. By calling this method, the caller gives the 64 // instruction to connect to the given node (if it hasn't already) and ask it to 65 // send (or re-send) up-to-date information about the specified range. Having 66 // done so, the information should soon thereafter be available to the Sink and 67 // from there, further follower read attempts. Does not block. 68 func (pr *Clients) Request(nodeID roachpb.NodeID, rangeID roachpb.RangeID) { 69 if nodeID == pr.cfg.NodeID { 70 return 71 } 72 if cl := pr.getOrCreateClient(nodeID); cl != nil { 73 cl.mu.Lock() 74 cl.mu.requested[rangeID] = struct{}{} 75 cl.mu.Unlock() 76 } 77 } 78 79 // EnsureClient makes sure that updates from the given nodes are pulled in, if 80 // they aren't already. This call does not block (and is cheap). 81 func (pr *Clients) EnsureClient(nodeID roachpb.NodeID) { 82 if nodeID == pr.cfg.NodeID { 83 return 84 } 85 pr.getOrCreateClient(nodeID) 86 } 87 88 func (pr *Clients) getOrCreateClient(nodeID roachpb.NodeID) *client { 89 // Fast path to check for existing client without an allocation. 90 p, found := pr.clients.Load(int64(nodeID)) 91 cl := (*client)(p) 92 if found { 93 return cl 94 } 95 if !pr.cfg.Dialer.Ready(nodeID) { 96 return nil 97 } 98 99 if nodeID == pr.cfg.NodeID { 100 panic("must not create client to local node") 101 } 102 103 // Slow path: create the client. Another inserter might race us to it. 104 105 // This allocates, so only do it when necessary. 106 ctx := logtags.AddTag(context.Background(), "ct-client", "") 107 108 cl = &client{} 109 cl.mu.requested = map[roachpb.RangeID]struct{}{} 110 111 if firstClient, loaded := pr.clients.LoadOrStore(int64(nodeID), unsafe.Pointer(cl)); loaded { 112 return (*client)(firstClient) 113 } 114 115 // If our client made it into the map, start it. The point in inserting 116 // before starting is to be able to collect RangeIDs immediately while never 117 // blocking callers. 118 pr.cfg.Stopper.RunWorker(ctx, func(ctx context.Context) { 119 defer pr.clients.Delete(int64(nodeID)) 120 121 c, err := pr.cfg.Dialer.Dial(ctx, nodeID) 122 if err != nil { 123 if log.V(1) { 124 log.Warningf(ctx, "error opening closed timestamp stream to n%d: %+v", nodeID, err) 125 } 126 return 127 } 128 defer func() { 129 _ = c.CloseSend() 130 }() 131 132 ctx = c.Context() 133 134 ch := pr.cfg.Sink.Notify(nodeID) 135 defer close(ch) 136 137 reaction := &ctpb.Reaction{} 138 for { 139 if err := c.Send(reaction); err != nil { 140 return 141 } 142 entry, err := c.Recv() 143 if err != nil { 144 return 145 } 146 147 select { 148 case ch <- *entry: 149 case <-ctx.Done(): 150 return 151 case <-pr.cfg.Stopper.ShouldQuiesce(): 152 return 153 } 154 155 var requested map[roachpb.RangeID]struct{} 156 cl.mu.Lock() 157 requested, cl.mu.requested = cl.mu.requested, map[roachpb.RangeID]struct{}{} 158 cl.mu.Unlock() 159 160 slice := make([]roachpb.RangeID, 0, len(requested)) 161 for rangeID := range requested { 162 slice = append(slice, rangeID) 163 } 164 reaction = &ctpb.Reaction{ 165 Requested: slice, 166 } 167 } 168 }) 169 170 return cl 171 }