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 }