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  }