go.uber.org/yarpc@v1.72.1/peer/abstractlist/list.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package abstractlist
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"math/rand"
    27  	"sort"
    28  	"strconv"
    29  	"sync"
    30  	"time"
    31  
    32  	"go.uber.org/atomic"
    33  	"go.uber.org/multierr"
    34  	"go.uber.org/yarpc/api/peer"
    35  	"go.uber.org/yarpc/api/transport"
    36  	"go.uber.org/yarpc/api/x/introspection"
    37  	intyarpcerrors "go.uber.org/yarpc/internal/yarpcerrors"
    38  	"go.uber.org/yarpc/pkg/lifecycle"
    39  	"go.uber.org/yarpc/yarpcerrors"
    40  	"go.uber.org/zap"
    41  )
    42  
    43  // Implementation is a collection of available peers, with its own
    44  // subscribers for pending request count change notifications.
    45  // The abstract list uses the implementation to track peers that can be
    46  // returned by Choose, as opposed to those that were added but reported
    47  // unavailable by the underlying transport.
    48  // Use "go.uber.org/yarpc/peer/abstractlist".List in conjunction with an
    49  // Implementation to produce a "go.uber.org/yarpc/api/peer".ChooserList.
    50  //
    51  // The abstractlist.List calls Add, Remove, and Choose under a write lock so
    52  // the implementation is free to perform mutations on its own data without
    53  // locks.
    54  //
    55  // It should be noted that since the abstractlist.List is using a write
    56  // lock for Add, Remove and Choose, performances of the abstract.List
    57  // might be limited for a given implementation.
    58  // For instance, a tworandomchoices implementation would not need
    59  // a write lock for the method Choose but only a read lock.
    60  //
    61  // Choose must return nil immediately if the collection is empty.
    62  // The abstractlist.List guarantees that peers will only be added if they're
    63  // absent, and only removed they are present.
    64  // Choose should not block.
    65  type Implementation interface {
    66  	Add(peer.StatusPeer, peer.Identifier) Subscriber
    67  	Remove(peer.StatusPeer, peer.Identifier, Subscriber)
    68  	Choose(*transport.Request) peer.StatusPeer
    69  }
    70  
    71  // Subscriber is a callback that implementations of peer list data structures
    72  // must provide.
    73  //
    74  // The peer list uses the Subscriber to send notifications when a peer’s
    75  // pending request count changes.
    76  // A peer list implementation may have a single subscriber or a subscriber for
    77  // each peer.
    78  // UpdatePendingRequestCount is thread safe to be called
    79  type Subscriber interface {
    80  	UpdatePendingRequestCount(int)
    81  }
    82  
    83  type options struct {
    84  	capacity             int
    85  	defaultChooseTimeout time.Duration
    86  	noShuffle            bool
    87  	failFast             bool
    88  	seed                 int64
    89  	logger               *zap.Logger
    90  }
    91  
    92  var defaultOptions = options{
    93  	defaultChooseTimeout: 500 * time.Millisecond,
    94  	capacity:             10,
    95  	seed:                 time.Now().UnixNano(),
    96  }
    97  
    98  // Option customizes the behavior of a list.
    99  type Option interface {
   100  	apply(*options)
   101  }
   102  
   103  type optionFunc func(*options)
   104  
   105  func (f optionFunc) apply(options *options) { f(options) }
   106  
   107  // Capacity specifies the default capacity of the underlying
   108  // data structures for this list
   109  //
   110  // Defaults to 10.
   111  func Capacity(capacity int) Option {
   112  	return optionFunc(func(options *options) {
   113  		options.capacity = capacity
   114  	})
   115  }
   116  
   117  // Logger specifies a logger.
   118  func Logger(logger *zap.Logger) Option {
   119  	return optionFunc(func(options *options) {
   120  		options.logger = logger
   121  	})
   122  }
   123  
   124  // NoShuffle disables the default behavior of shuffling peer list order.
   125  func NoShuffle() Option {
   126  	return optionFunc(func(options *options) {
   127  		options.noShuffle = true
   128  	})
   129  }
   130  
   131  // FailFast indicates that the peer list should not wait for peers to be added,
   132  // when choosing a peer.
   133  //
   134  // This option is particularly useful for proxies.
   135  func FailFast() Option {
   136  	return optionFunc(func(options *options) {
   137  		options.failFast = true
   138  	})
   139  }
   140  
   141  // Seed specifies the random seed to use for shuffling peers
   142  //
   143  // Defaults to approximately the process start time in nanoseconds.
   144  func Seed(seed int64) Option {
   145  	return optionFunc(func(options *options) {
   146  		options.seed = seed
   147  	})
   148  }
   149  
   150  // DefaultChooseTimeout specifies the default timeout to add to 'Choose' calls
   151  // without context deadlines. This prevents long-lived streams from setting
   152  // calling deadlines.
   153  //
   154  // Defaults to 500ms.
   155  func DefaultChooseTimeout(timeout time.Duration) Option {
   156  	return optionFunc(func(options *options) {
   157  		options.defaultChooseTimeout = timeout
   158  	})
   159  }
   160  
   161  // New creates a new peer list with an identifier chooser for available peers.
   162  func New(name string, transport peer.Transport, implementation Implementation, opts ...Option) *List {
   163  	options := defaultOptions
   164  	for _, o := range opts {
   165  		o.apply(&options)
   166  	}
   167  
   168  	logger := options.logger
   169  	if logger == nil {
   170  		logger = zap.NewNop()
   171  	}
   172  
   173  	return &List{
   174  		once:               lifecycle.NewOnce(),
   175  		name:               name,
   176  		logger:             logger,
   177  		peers:              make(map[string]*peerFacade, options.capacity),
   178  		offlinePeers:       make(map[string]peer.Identifier, options.capacity),
   179  		implementation:     implementation,
   180  		transport:          transport,
   181  		noShuffle:          options.noShuffle,
   182  		failFast:           options.failFast,
   183  		randSrc:            rand.NewSource(options.seed),
   184  		peerAvailableEvent: make(chan struct{}, 1),
   185  	}
   186  }
   187  
   188  // List is an abstract peer list, backed by an Implementation to
   189  // determine which peer to choose among available peers.
   190  // The abstract list manages available versus unavailable peers, intercepting
   191  // these notifications from the transport's concrete implementation of
   192  // peer.StatusPeer with the peer.Subscriber API.
   193  // The peer list will not choose an unavailable peer, prefering to block until
   194  // one becomes available.
   195  //
   196  // The list is a suitable basis for concrete implementations like round-robin.
   197  //
   198  // This abstract list does not participate in the transport’s pending request
   199  // count tracking.
   200  // The list tracks pending request counts for the peers that it chooses, does
   201  // not inform the transport of these choices, and ignores notifications from
   202  // the transport about choices other peer lists that share the same peers have
   203  // made.
   204  type List struct {
   205  	lock sync.RWMutex
   206  	once *lifecycle.Once
   207  
   208  	name   string
   209  	logger *zap.Logger
   210  
   211  	peers              map[string]*peerFacade
   212  	offlinePeers       map[string]peer.Identifier
   213  	numPeers           atomic.Int32
   214  	numAvailable       atomic.Int32
   215  	implementation     Implementation
   216  	peerAvailableEvent chan struct{}
   217  	transport          peer.Transport
   218  
   219  	defaultChooseTimeout time.Duration
   220  	noShuffle            bool
   221  	failFast             bool
   222  	randSrc              rand.Source
   223  }
   224  
   225  // Name returns the name of the list.
   226  func (pl *List) Name() string { return pl.name }
   227  
   228  // Transport returns the underlying transport for retaining and releasing peers.
   229  func (pl *List) Transport() peer.Transport { return pl.transport }
   230  
   231  // Update applies the additions and removals of peer Identifiers to the list
   232  // it returns a multi-error result of every failure that happened without
   233  // circuit breaking due to failures.
   234  //
   235  // Updates must be serialized so no peer is removed if it is absent and no peer
   236  // is added if it is present.
   237  // Updates should not have overlapping additions and removals, but the list
   238  // will tollerate this case, but may cause existing connections to close and be
   239  // replaced.
   240  //
   241  // Update will return errors if its invariants are violated, regardless of
   242  // whether updates are sent while the list is running.
   243  // Updates may be interleaved with Start and Stop in any order any number of
   244  // times.
   245  func (pl *List) Update(updates peer.ListUpdates) error {
   246  	pl.logger.Debug("peer list update",
   247  		zap.Int("additions", len(updates.Additions)),
   248  		zap.Int("removals", len(updates.Removals)))
   249  
   250  	if len(updates.Additions) == 0 && len(updates.Removals) == 0 {
   251  		return nil
   252  	}
   253  
   254  	pl.lock.Lock()
   255  	defer pl.lock.Unlock()
   256  
   257  	if !pl.once.IsRunning() {
   258  		return pl.updateOffline(updates)
   259  	}
   260  	return pl.updateOnline(updates)
   261  }
   262  
   263  // updateOnline must be run under a list lock.
   264  func (pl *List) updateOnline(updates peer.ListUpdates) error {
   265  	var errs error
   266  	for _, id := range updates.Removals {
   267  		errs = multierr.Append(errs, pl.remove(id))
   268  	}
   269  
   270  	add := updates.Additions
   271  	if !pl.noShuffle {
   272  		add = shuffle(pl.randSrc, add)
   273  	}
   274  
   275  	for _, id := range add {
   276  		errs = multierr.Append(errs, pl.add(id))
   277  	}
   278  	return errs
   279  }
   280  
   281  // updateOffline must be run under a list lock.
   282  func (pl *List) updateOffline(updates peer.ListUpdates) error {
   283  	var errs error
   284  	for _, id := range updates.Removals {
   285  		errs = multierr.Append(errs, pl.removeOffline(id))
   286  	}
   287  	for _, id := range updates.Additions {
   288  		errs = multierr.Append(errs, pl.addOffline(id))
   289  	}
   290  	return errs
   291  }
   292  
   293  // Start notifies the List that requests will start coming
   294  func (pl *List) Start() error {
   295  	return pl.once.Start(pl.start)
   296  }
   297  
   298  func (pl *List) start() error {
   299  	pl.lock.Lock()
   300  	defer pl.lock.Unlock()
   301  
   302  	all := pl.offlinePeerIdentifiers()
   303  
   304  	var err error
   305  	err = multierr.Append(err, pl.updateOffline(peer.ListUpdates{
   306  		Removals: all,
   307  	}))
   308  	err = multierr.Append(err, pl.updateOnline(peer.ListUpdates{
   309  		Additions: all,
   310  	}))
   311  	return err
   312  }
   313  
   314  // Stop notifies the List that requests will stop coming
   315  func (pl *List) Stop() error {
   316  	return pl.once.Stop(pl.stop)
   317  }
   318  
   319  func (pl *List) stop() error {
   320  	pl.lock.Lock()
   321  	defer pl.lock.Unlock()
   322  
   323  	all := pl.onlinePeerIdentifiers()
   324  
   325  	var err error
   326  	err = multierr.Append(err, pl.updateOnline(peer.ListUpdates{
   327  		Removals: all,
   328  	}))
   329  	err = multierr.Append(err, pl.updateOffline(peer.ListUpdates{
   330  		Additions: all,
   331  	}))
   332  	return err
   333  }
   334  
   335  // IsRunning returns whether the peer list is running.
   336  func (pl *List) IsRunning() bool {
   337  	return pl.once.IsRunning()
   338  }
   339  
   340  // add retains a peer and sets up a facade (a thin proxy for a peer) to receive
   341  // connection status notifications from the dialer and track pending request
   342  // counts.
   343  //
   344  // add does not add the peer to the list of peers available for choosing (the
   345  // Implementation).
   346  // The facade is responsible for adding and removing the peer from the
   347  // collection of available peers based on connection status notifications.
   348  //
   349  // add must be run inside a list lock.
   350  func (pl *List) add(id peer.Identifier) error {
   351  	addr := id.Identifier()
   352  
   353  	if _, ok := pl.peers[addr]; ok {
   354  		return peer.ErrPeerAddAlreadyInList(addr)
   355  	}
   356  
   357  	pf := &peerFacade{list: pl, id: id}
   358  	pf.onFinish = pl.onFinishFunc(pf)
   359  
   360  	// The transport must not call back before returning.
   361  	p, err := pl.transport.RetainPeer(id, pf)
   362  	if err != nil {
   363  		return err
   364  	}
   365  
   366  	pf.peer = p
   367  	pl.peers[addr] = pf
   368  	pl.numPeers.Inc()
   369  	pl.notifyStatusChanged(pf)
   370  
   371  	return nil
   372  }
   373  
   374  // addOffline must be run under a list lock.
   375  func (pl *List) addOffline(id peer.Identifier) error {
   376  	addr := id.Identifier()
   377  
   378  	if _, ok := pl.offlinePeers[addr]; ok {
   379  		return peer.ErrPeerAddAlreadyInList(addr)
   380  	}
   381  
   382  	pl.offlinePeers[addr] = id
   383  	return nil
   384  }
   385  
   386  // remove releases and forgets a peer.
   387  //
   388  // remove must be run under a list lock.
   389  func (pl *List) remove(id peer.Identifier) error {
   390  	addr := id.Identifier()
   391  
   392  	pf, ok := pl.peers[addr]
   393  	if !ok {
   394  		return peer.ErrPeerRemoveNotInList(addr)
   395  	}
   396  
   397  	if pf.status.ConnectionStatus == peer.Available {
   398  		pl.numAvailable.Dec()
   399  		pl.implementation.Remove(pf, pf.id, pf.subscriber)
   400  		pf.subscriber = nil
   401  	}
   402  	pf.status.ConnectionStatus = peer.Unavailable
   403  
   404  	pl.numPeers.Dec()
   405  	delete(pl.peers, addr)
   406  
   407  	// The transport must not call back before returning.
   408  	return pl.transport.ReleasePeer(id, pf)
   409  }
   410  
   411  func (pl *List) removeOffline(id peer.Identifier) error {
   412  	addr := id.Identifier()
   413  
   414  	_, ok := pl.offlinePeers[addr]
   415  	if !ok {
   416  		return peer.ErrPeerRemoveNotInList(addr)
   417  	}
   418  
   419  	delete(pl.offlinePeers, addr)
   420  
   421  	return nil
   422  }
   423  
   424  // Choose selects the next available peer in the peer list.
   425  func (pl *List) Choose(ctx context.Context, req *transport.Request) (peer.Peer, func(error), error) {
   426  	if _, ok := ctx.Deadline(); !ok {
   427  		// set the default timeout on the chooser so that we do not wait
   428  		// indefinitely for a peer to become available
   429  		var cancel context.CancelFunc
   430  		ctx, cancel = context.WithTimeout(ctx, pl.defaultChooseTimeout)
   431  		defer cancel()
   432  	}
   433  	// We wait for the chooser to start and produce an error if the list does
   434  	// not start before the context deadline times out.
   435  	// This ensures that the developer sees a meaningful error if they forget
   436  	// to run the lifecycle methods.
   437  	if err := pl.once.WaitUntilRunning(ctx); err != nil {
   438  		return nil, nil, intyarpcerrors.AnnotateWithInfo(yarpcerrors.FromError(err), "%q peer list is not running", pl.name)
   439  	}
   440  
   441  	// Choose runs without a lock because it spends the bulk of its time in a
   442  	// wait loop.
   443  	for {
   444  		p := pl.choose(req)
   445  		// choose signals that there are no available peers by returning nil.
   446  		// Thereafter, every Choose call will wait for a peer or peers to
   447  		// become available again.
   448  		// We reach for an available peer optimistically, resorting to waiting
   449  		// for a notification only if the underlying list is empty.
   450  		if p != nil {
   451  			// We call notifyPeerAvailable because there is a chance that more
   452  			// than one chooser is blocked in waitForPeerAddedEvent.
   453  			// Once a peer becomes available, all of these goroutines should
   454  			// resume, not just one, until no peers are available again.
   455  			// The underlying channel has a limited capacity, so every success
   456  			// must trigger the rest to resume.
   457  			pl.notifyPeerAvailable()
   458  			pf := p.(*peerFacade)
   459  			pl.onStart(pf)
   460  			return pf.peer, pf.onFinish, nil
   461  		}
   462  		if pl.failFast {
   463  			return nil, nil, pl.newUnavailableError(nil)
   464  		}
   465  		if err := pl.waitForPeerAddedEvent(ctx); err != nil {
   466  			return nil, nil, err
   467  		}
   468  	}
   469  }
   470  
   471  // choose guards the underlying implementation's consistency around a lock, and
   472  // recovers the lock if the underlying list panics.
   473  func (pl *List) choose(req *transport.Request) peer.StatusPeer {
   474  	// Even if all of the implementation provided by yarpc
   475  	// implements their own locking system - since v1.50.0
   476  	// this lock is needed for supporting potential
   477  	// implementation defined outside of yarpc
   478  	pl.lock.Lock()
   479  	defer pl.lock.Unlock()
   480  
   481  	return pl.implementation.Choose(req)
   482  }
   483  
   484  func (pl *List) onStart(pf *peerFacade) {
   485  	pl.lock.Lock()
   486  	defer pl.lock.Unlock()
   487  
   488  	pf.status.PendingRequestCount++
   489  	if pf.subscriber != nil {
   490  		pf.subscriber.UpdatePendingRequestCount(pf.status.PendingRequestCount)
   491  	}
   492  }
   493  
   494  func (pl *List) onFinish(pf *peerFacade, err error) {
   495  	pl.lock.Lock()
   496  	defer pl.lock.Unlock()
   497  
   498  	pf.status.PendingRequestCount--
   499  	if pf.subscriber != nil {
   500  		pf.subscriber.UpdatePendingRequestCount(pf.status.PendingRequestCount)
   501  	}
   502  }
   503  
   504  func (pl *List) onFinishFunc(pf *peerFacade) func(error) {
   505  	return func(err error) {
   506  		pl.onFinish(pf, err)
   507  	}
   508  }
   509  
   510  // NotifyStatusChanged receives status change notifications for peers in the
   511  // list.
   512  //
   513  // This function exists only as is necessary for dispatching connection status
   514  // changes from tests.
   515  func (pl *List) NotifyStatusChanged(pid peer.Identifier) {
   516  	pl.lock.Lock()
   517  	defer pl.lock.Unlock()
   518  
   519  	pf := pl.peers[pid.Identifier()]
   520  	pl.notifyStatusChanged(pf)
   521  }
   522  
   523  func (pl *List) lockAndNotifyStatusChanged(pf *peerFacade) {
   524  	pl.lock.Lock()
   525  	defer pl.lock.Unlock()
   526  
   527  	pl.notifyStatusChanged(pf)
   528  }
   529  
   530  func (pl *List) status(pf *peerFacade) peer.Status {
   531  	pl.lock.RLock()
   532  	defer pl.lock.RUnlock()
   533  
   534  	return pf.status
   535  }
   536  
   537  // notifyStatusChanged must be run under a list lock.
   538  func (pl *List) notifyStatusChanged(pf *peerFacade) {
   539  	if pf == nil {
   540  		return
   541  	}
   542  
   543  	status := pf.peer.Status().ConnectionStatus
   544  	if pf.status.ConnectionStatus != status {
   545  		pf.status.ConnectionStatus = status
   546  		switch status {
   547  		case peer.Available:
   548  			sub := pf.list.implementation.Add(pf, pf.id)
   549  			pf.subscriber = sub
   550  			pl.numAvailable.Inc()
   551  			pf.list.notifyPeerAvailable()
   552  		default:
   553  			pl.numAvailable.Dec()
   554  			pf.list.implementation.Remove(pf, pf.id, pf.subscriber)
   555  			pf.subscriber = nil
   556  		}
   557  	}
   558  }
   559  
   560  // notifyPeerAvailable writes to a channel indicating that a Peer is currently
   561  // available for requests.
   562  //
   563  // notifyPeerAvailable may be called without a list lock.
   564  func (pl *List) notifyPeerAvailable() {
   565  	select {
   566  	case pl.peerAvailableEvent <- struct{}{}:
   567  	default:
   568  	}
   569  }
   570  
   571  // waitForPeerAddedEvent waits until a peer is added to the peer list or the
   572  // given context finishes.
   573  //
   574  // waitForPeerAddedEvent must not be run under a lock.
   575  func (pl *List) waitForPeerAddedEvent(ctx context.Context) error {
   576  	select {
   577  	case <-pl.peerAvailableEvent:
   578  		return nil
   579  	case <-ctx.Done():
   580  		return pl.newUnavailableError(ctx.Err())
   581  	}
   582  }
   583  
   584  func (pl *List) newUnavailableError(err error) error {
   585  	return yarpcerrors.Newf(yarpcerrors.CodeUnavailable, "%q peer list %s", pl.name, pl.unavailableErrorMessage(err))
   586  }
   587  
   588  func (pl *List) unavailableErrorMessage(err error) string {
   589  	num := int(pl.numPeers.Load())
   590  	if num == 0 {
   591  		return "has no peers, " + pl.noPeersMessage(err)
   592  	}
   593  	if num == 1 {
   594  		return "has 1 peer but it is not responsive, " + pl.unavailablePeersMessage(err)
   595  	}
   596  	return "has " + strconv.Itoa(num) + " peers but none are responsive, " + pl.unavailablePeersMessage(err)
   597  }
   598  
   599  func (pl *List) noPeersMessage(err error) string {
   600  	if pl.failFast {
   601  		return "did not wait for peers to be added (fail-fast is enabled)"
   602  	}
   603  	return "waited for peers to be added but timed out (fail-fast is not enabled): " + err.Error()
   604  }
   605  
   606  func (pl *List) unavailablePeersMessage(err error) string {
   607  	if pl.failFast {
   608  		return "did not wait for a connection to open (fail-fast is enabled)"
   609  	}
   610  	return "timed out waiting for a connection to open (fail-fast is not enabled): " + err.Error()
   611  }
   612  
   613  // NumAvailable returns how many peers are available.
   614  func (pl *List) NumAvailable() int {
   615  	return int(pl.numAvailable.Load())
   616  }
   617  
   618  // NumUnavailable returns how many peers are unavailable while the list is
   619  // running.
   620  func (pl *List) NumUnavailable() int {
   621  	// Although we have atomics, we still need the lock to capture a consistent
   622  	// snapshot.
   623  	pl.lock.RLock()
   624  	defer pl.lock.RUnlock()
   625  
   626  	return int(pl.numPeers.Load() - pl.numAvailable.Load())
   627  }
   628  
   629  // NumUninitialized returns how many peers are unavailable because the peer
   630  // list was stopped or has not yet started.
   631  func (pl *List) NumUninitialized() int {
   632  	pl.lock.RLock()
   633  	defer pl.lock.RUnlock()
   634  
   635  	return len(pl.offlinePeers)
   636  }
   637  
   638  // Available returns whether the identifier peer is available for traffic.
   639  func (pl *List) Available(pid peer.Identifier) bool {
   640  	pl.lock.RLock()
   641  	defer pl.lock.RUnlock()
   642  
   643  	if pf, ok := pl.peers[pid.Identifier()]; ok {
   644  		return pf.status.ConnectionStatus == peer.Available
   645  	}
   646  	return false
   647  }
   648  
   649  // Uninitialized returns whether the identifier peer is present but uninitialized.
   650  func (pl *List) Uninitialized(pid peer.Identifier) bool {
   651  	pl.lock.RLock()
   652  	defer pl.lock.RUnlock()
   653  
   654  	_, exists := pl.offlinePeers[pid.Identifier()]
   655  	return exists
   656  }
   657  
   658  // Peers returns a snapshot of all retained (available and unavailable) peers.
   659  func (pl *List) Peers() []peer.StatusPeer {
   660  	pl.lock.RLock()
   661  	defer pl.lock.RUnlock()
   662  
   663  	peers := make([]peer.StatusPeer, 0, len(pl.peers))
   664  	for _, pf := range pl.peers {
   665  		peers = append(peers, pf.peer)
   666  	}
   667  	return peers
   668  }
   669  
   670  func (pl *List) onlinePeerIdentifiers() []peer.Identifier {
   671  	// This is not duplicate code with offlinePeerIdentifiers, as it traverses
   672  	// peers instead of offlinePeers.
   673  	addrs := make([]string, 0, len(pl.peers))
   674  	for addr := range pl.peers {
   675  		addrs = append(addrs, addr)
   676  	}
   677  	sort.Strings(addrs)
   678  
   679  	ids := make([]peer.Identifier, len(addrs))
   680  	for i, addr := range addrs {
   681  		ids[i] = pl.peers[addr].peer
   682  	}
   683  	return ids
   684  }
   685  
   686  func (pl *List) offlinePeerIdentifiers() []peer.Identifier {
   687  	// This is not duplicate code with offlinePeerIdentifiers, as it traverses
   688  	// offlinePeers instead of onlinePeers.
   689  	addrs := make([]string, 0, len(pl.offlinePeers))
   690  	for addr := range pl.offlinePeers {
   691  		addrs = append(addrs, addr)
   692  	}
   693  	sort.Strings(addrs)
   694  
   695  	ids := make([]peer.Identifier, len(addrs))
   696  	for i, addr := range addrs {
   697  		id := pl.offlinePeers[addr]
   698  		ids[i] = id
   699  	}
   700  	return ids
   701  }
   702  
   703  // Introspect returns a ChooserStatus with a summary of the Peers.
   704  func (pl *List) Introspect() introspection.ChooserStatus {
   705  	pl.lock.RLock()
   706  	defer pl.lock.RUnlock()
   707  
   708  	available := 0
   709  	unavailable := 0
   710  	for _, pf := range pl.peers {
   711  		if pf.status.ConnectionStatus == peer.Available {
   712  			available++
   713  		} else {
   714  			unavailable++
   715  		}
   716  	}
   717  
   718  	peerStatuses := make([]introspection.PeerStatus, 0,
   719  		len(pl.peers))
   720  
   721  	buildPeerStatus := func(pf *peerFacade) introspection.PeerStatus {
   722  		ps := pf.status
   723  		return introspection.PeerStatus{
   724  			Identifier: pf.peer.Identifier(),
   725  			State: fmt.Sprintf("%s, %d pending request(s)",
   726  				ps.ConnectionStatus.String(),
   727  				ps.PendingRequestCount),
   728  		}
   729  	}
   730  
   731  	for _, pf := range pl.peers {
   732  		peerStatuses = append(peerStatuses, buildPeerStatus(pf))
   733  	}
   734  
   735  	return introspection.ChooserStatus{
   736  		Name: pl.name,
   737  		State: fmt.Sprintf("%s (%d/%d available)", pl.once.State(), available,
   738  			available+unavailable),
   739  		Peers: peerStatuses,
   740  	}
   741  }
   742  
   743  // shuffle randomizes the order of a slice of peers.
   744  // see: https://en.wikipedia.org/wiki/Fisher-Yates_shuffle
   745  func shuffle(src rand.Source, in []peer.Identifier) []peer.Identifier {
   746  	shuffled := make([]peer.Identifier, len(in))
   747  	r := rand.New(src)
   748  	copy(shuffled, in)
   749  	for i := len(in) - 1; i > 0; i-- {
   750  		j := r.Intn(i + 1)
   751  		shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
   752  	}
   753  	return shuffled
   754  }