github.com/uber/kraken@v0.1.4/lib/torrent/scheduler/connstate/state.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package connstate
    15  
    16  import (
    17  	"errors"
    18  	"time"
    19  
    20  	"github.com/andres-erbsen/clock"
    21  	"github.com/uber/kraken/core"
    22  	"github.com/uber/kraken/lib/torrent/networkevent"
    23  	"github.com/uber/kraken/lib/torrent/scheduler/conn"
    24  	"go.uber.org/zap"
    25  )
    26  
    27  // State errors.
    28  var (
    29  	ErrTorrentAtCapacity       = errors.New("torrent is at capacity")
    30  	ErrConnAlreadyPending      = errors.New("conn is already pending")
    31  	ErrConnAlreadyActive       = errors.New("conn is already active")
    32  	ErrConnClosed              = errors.New("conn is closed")
    33  	ErrInvalidActiveTransition = errors.New("conn must be pending to transition to active")
    34  	ErrTooManyMutualConns      = errors.New("conn has too many mutual connections")
    35  
    36  	// This should NEVER happen.
    37  	errUnknownStatus = errors.New("invariant violation: unknown status")
    38  )
    39  
    40  type status int
    41  
    42  const (
    43  	// _uninit indicates the connection is uninitialized. This is the default
    44  	// status for empty entries.
    45  	_uninit status = iota
    46  	_pending
    47  	_active
    48  )
    49  
    50  type entry struct {
    51  	status status
    52  	conn   *conn.Conn
    53  }
    54  
    55  type connKey struct {
    56  	hash   core.InfoHash
    57  	peerID core.PeerID
    58  }
    59  
    60  type blacklistEntry struct {
    61  	expiration time.Time
    62  }
    63  
    64  func (e *blacklistEntry) Blacklisted(now time.Time) bool {
    65  	return e.Remaining(now) > 0
    66  }
    67  
    68  func (e *blacklistEntry) Remaining(now time.Time) time.Duration {
    69  	return e.expiration.Sub(now)
    70  }
    71  
    72  // State provides connection lifecycle management and enforces connection
    73  // limits. A connection to a peer is identified by torrent info hash and peer id.
    74  // Each connection may exist in the following states: pending, active, or
    75  // blacklisted. Pending connections are unestablished connections which "reserve"
    76  // connection capacity until they are done handshaking. Active connections are
    77  // established connections. Blacklisted connections are failed connections which
    78  // should be skipped in each peer handout.
    79  //
    80  // Note, State is NOT thread-safe. Synchronization must be provided by the client.
    81  type State struct {
    82  	config      Config
    83  	clk         clock.Clock
    84  	netevents   networkevent.Producer
    85  	localPeerID core.PeerID
    86  	logger      *zap.SugaredLogger
    87  
    88  	// All pending or active conns. These count towards conn capacity.
    89  	conns map[core.InfoHash]map[core.PeerID]entry
    90  
    91  	// All blacklisted conns. These do not count towards conn capacity.
    92  	blacklist map[connKey]*blacklistEntry
    93  }
    94  
    95  // New creates a new State.
    96  func New(
    97  	config Config,
    98  	clk clock.Clock,
    99  	localPeerID core.PeerID,
   100  	netevents networkevent.Producer,
   101  	logger *zap.SugaredLogger) *State {
   102  
   103  	config = config.applyDefaults()
   104  
   105  	return &State{
   106  		config:      config,
   107  		clk:         clk,
   108  		netevents:   netevents,
   109  		localPeerID: localPeerID,
   110  		logger:      logger,
   111  		conns:       make(map[core.InfoHash]map[core.PeerID]entry),
   112  		blacklist:   make(map[connKey]*blacklistEntry),
   113  	}
   114  }
   115  
   116  // ActiveConns returns a list of all active connections.
   117  func (s *State) ActiveConns() []*conn.Conn {
   118  	var active []*conn.Conn
   119  	for _, peers := range s.conns {
   120  		for _, e := range peers {
   121  			if e.status == _active {
   122  				active = append(active, e.conn)
   123  			}
   124  		}
   125  	}
   126  	return active
   127  }
   128  
   129  // Saturated returns true if h is at capacity and all the conns are active.
   130  func (s *State) Saturated(h core.InfoHash) bool {
   131  	peers, ok := s.conns[h]
   132  	if !ok {
   133  		return false
   134  	}
   135  	var active int
   136  	for _, e := range peers {
   137  		if e.status == _active {
   138  			active++
   139  		}
   140  	}
   141  	return active == s.config.MaxOpenConnectionsPerTorrent
   142  }
   143  
   144  // Blacklist blacklists peerID/h for the configured BlacklistDuration.
   145  // Returns error if the connection is already blacklisted.
   146  func (s *State) Blacklist(peerID core.PeerID, h core.InfoHash) error {
   147  	if s.config.DisableBlacklist {
   148  		return nil
   149  	}
   150  
   151  	k := connKey{h, peerID}
   152  	if e, ok := s.blacklist[k]; ok && e.Blacklisted(s.clk.Now()) {
   153  		return errors.New("conn is already blacklisted")
   154  	}
   155  	s.blacklist[k] = &blacklistEntry{s.clk.Now().Add(s.config.BlacklistDuration)}
   156  
   157  	s.log("peer", peerID, "hash", h).Infof(
   158  		"Connection blacklisted for %s", s.config.BlacklistDuration)
   159  	s.netevents.Produce(
   160  		networkevent.BlacklistConnEvent(h, s.localPeerID, peerID, s.config.BlacklistDuration))
   161  
   162  	return nil
   163  }
   164  
   165  // Blacklisted returns true if peerID/h is blacklisted.
   166  func (s *State) Blacklisted(peerID core.PeerID, h core.InfoHash) bool {
   167  	e, ok := s.blacklist[connKey{h, peerID}]
   168  	return ok && e.Blacklisted(s.clk.Now())
   169  }
   170  
   171  // ClearBlacklist un-blacklists all connections for h.
   172  func (s *State) ClearBlacklist(h core.InfoHash) {
   173  	for k := range s.blacklist {
   174  		if k.hash == h {
   175  			delete(s.blacklist, k)
   176  		}
   177  	}
   178  }
   179  
   180  // AddPending sets the connection for peerID/h as pending and reserves capacity
   181  // for it.
   182  func (s *State) AddPending(peerID core.PeerID, h core.InfoHash, neighbors []core.PeerID) error {
   183  	if len(s.conns[h]) == s.config.MaxOpenConnectionsPerTorrent {
   184  		return ErrTorrentAtCapacity
   185  	}
   186  	switch s.get(h, peerID).status {
   187  	case _uninit:
   188  		if s.numMutualConns(h, neighbors) > s.config.MaxMutualConnections {
   189  			return ErrTooManyMutualConns
   190  		}
   191  		s.put(h, peerID, entry{status: _pending})
   192  		s.log("hash", h, "peer", peerID).Infof(
   193  			"Added pending conn, capacity now at %d", s.capacity(h))
   194  		return nil
   195  	case _pending:
   196  		return ErrConnAlreadyPending
   197  	case _active:
   198  		return ErrConnAlreadyActive
   199  	default:
   200  		return errUnknownStatus
   201  	}
   202  }
   203  
   204  // DeletePending deletes the pending connection for peerID/h and frees capacity.
   205  func (s *State) DeletePending(peerID core.PeerID, h core.InfoHash) {
   206  	if s.get(h, peerID).status != _pending {
   207  		return
   208  	}
   209  	s.delete(h, peerID)
   210  	s.log("hash", h, "peer", peerID).Infof(
   211  		"Deleted pending conn, capacity now at %d", s.capacity(h))
   212  }
   213  
   214  // MovePendingToActive sets a previously pending connection as active.
   215  func (s *State) MovePendingToActive(c *conn.Conn) error {
   216  	if c.IsClosed() {
   217  		return ErrConnClosed
   218  	}
   219  	if s.get(c.InfoHash(), c.PeerID()).status != _pending {
   220  		return ErrInvalidActiveTransition
   221  	}
   222  	s.put(c.InfoHash(), c.PeerID(), entry{status: _active, conn: c})
   223  
   224  	s.log("hash", c.InfoHash(), "peer", c.PeerID()).Info("Moved conn from pending to active")
   225  	s.netevents.Produce(networkevent.AddActiveConnEvent(c.InfoHash(), s.localPeerID, c.PeerID()))
   226  
   227  	return nil
   228  }
   229  
   230  // DeleteActive deletes c. No-ops if c is not an active conn.
   231  func (s *State) DeleteActive(c *conn.Conn) {
   232  	e := s.get(c.InfoHash(), c.PeerID())
   233  	if e.status != _active {
   234  		return
   235  	}
   236  	if e.conn != c {
   237  		// It is possible that some new conn shares the same hash/peer as the old conn,
   238  		// so we need to make sure we're deleting the right one.
   239  		return
   240  	}
   241  	s.delete(c.InfoHash(), c.PeerID())
   242  
   243  	s.log("hash", c.InfoHash(), "peer", c.PeerID()).Infof(
   244  		"Deleted active conn, capacity now at %d", s.capacity(c.InfoHash()))
   245  	s.netevents.Produce(networkevent.DropActiveConnEvent(
   246  		c.InfoHash(), s.localPeerID, c.PeerID()))
   247  }
   248  
   249  func (s *State) numMutualConns(h core.InfoHash, neighbors []core.PeerID) int {
   250  	var n int
   251  	for _, id := range neighbors {
   252  		e := s.get(h, id)
   253  		if e.status == _pending || e.status == _active {
   254  			n++
   255  		}
   256  	}
   257  	return n
   258  }
   259  
   260  // BlacklistedConn represents a connection which has been blacklisted.
   261  type BlacklistedConn struct {
   262  	PeerID    core.PeerID   `json:"peer_id"`
   263  	InfoHash  core.InfoHash `json:"info_hash"`
   264  	Remaining time.Duration `json:"remaining"`
   265  }
   266  
   267  // BlacklistSnapshot returns a snapshot of all valid blacklist entries.
   268  func (s *State) BlacklistSnapshot() []BlacklistedConn {
   269  	var conns []BlacklistedConn
   270  	for k, e := range s.blacklist {
   271  		c := BlacklistedConn{
   272  			PeerID:    k.peerID,
   273  			InfoHash:  k.hash,
   274  			Remaining: e.Remaining(s.clk.Now()),
   275  		}
   276  		conns = append(conns, c)
   277  	}
   278  	return conns
   279  }
   280  
   281  func (s *State) get(h core.InfoHash, peerID core.PeerID) entry {
   282  	peers, ok := s.conns[h]
   283  	if !ok {
   284  		return entry{}
   285  	}
   286  	return peers[peerID]
   287  }
   288  
   289  func (s *State) put(h core.InfoHash, peerID core.PeerID, e entry) {
   290  	peers, ok := s.conns[h]
   291  	if !ok {
   292  		peers = make(map[core.PeerID]entry)
   293  		s.conns[h] = peers
   294  	}
   295  	peers[peerID] = e
   296  }
   297  
   298  func (s *State) delete(h core.InfoHash, peerID core.PeerID) {
   299  	peers, ok := s.conns[h]
   300  	if !ok {
   301  		return
   302  	}
   303  	delete(peers, peerID)
   304  	if len(peers) == 0 {
   305  		delete(s.conns, h)
   306  	}
   307  }
   308  
   309  func (s *State) capacity(h core.InfoHash) int {
   310  	return s.config.MaxOpenConnectionsPerTorrent - len(s.conns[h])
   311  }
   312  
   313  func (s *State) log(args ...interface{}) *zap.SugaredLogger {
   314  	return s.logger.With(args...)
   315  }