Class: QuartzTorrent::PeerManager

Inherits:
Object
  • Object
show all
Defined in:
lib/quartz_torrent/peermanager.rb

Overview

This class is used internally by PeerClient (The bittorrent protocol object) to choke, unchoke, and connect to peers for a specific torrent.

Instance Method Summary collapse

Constructor Details

#initializePeerManager

Returns a new instance of PeerManager.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/quartz_torrent/peermanager.rb', line 29

def initialize
  @logger = LogManager.getLogger("peer_manager")
  @targetActivePeerCount  = 50
  @targetUnchokedPeerCount = 4
  @cachedHandshakingAndEstablishedCount = 0
  # An array of Peers that we are allowing to download.
  @downloaders = []
  @optimisticUnchokePeer = nil
  # A peer is considered newly connected when the number of seconds it has had it's connection established
  # is below this number.
  @newlyConnectedDuration = 60
  @optimisticPeerChangeDuration = 30
  @lastOptimisticPeerChangeTime = nil
end

Instance Method Details

#manageConnections(classifiedPeers) ⇒ Object

Determine if we need to connect to more peers. Returns a list of peers to connect to.



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/quartz_torrent/peermanager.rb', line 46

def manageConnections(classifiedPeers)

  n = classifiedPeers.handshakingPeers.size + classifiedPeers.establishedPeers.size
  if n < @targetActivePeerCount
    result = classifiedPeers.disconnectedPeers.shuffle.first(@targetActivePeerCount - n)
    @logger.debug "There are #{n} peers connected or in handshaking. Will establish #{result.size} more connections to peers."
    result
  else
    []
  end
end

#managePeers(classifiedPeers) ⇒ Object

Given a list of Peer objects (torrent peers), calculate the actions to take.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/quartz_torrent/peermanager.rb', line 60

def managePeers(classifiedPeers)
  result = ManagePeersResult.new

  @logger.debug "Manage peers: #{classifiedPeers.disconnectedPeers.size} disconnected, #{classifiedPeers.handshakingPeers.size} handshaking, #{classifiedPeers.establishedPeers.size} established"
  @logger.debug "Manage peers: #{classifiedPeers}"

  # Unchoke some peers. According to the specification:
  #
  # "...unchoking the four peers which have the best upload rate and are interested.  These four peers are referred to as downloaders, because they are interested in downloading from the client."
  # "Peers which have a better upload rate (as compared to the downloaders) but aren't interested get unchoked. If they become interested, the downloader with the worst upload rate gets choked. 
  # If a client has a complete file, it uses its upload rate rather than its download rate to decide which peers to unchoke."
  # "at any one time there is a single peer which is unchoked regardless of its upload rate (if interested, it counts as one of the four allowed downloaders). Which peer is optimistically 
  # unchoked rotates every 30 seconds. Newly connected peers are three times as likely to start as the current optimistic unchoke as anywhere else in the rotation. This gives them a decent chance 
  # of getting a complete piece to upload."
  #
  # This doesn't define initial rampup; On rampup we have no peer upload rate information. 

  # We want to end up with:
  #   - At most 4 peers that are both interested and unchoked. These are Downloaders. They should be the ones with 
  #     the best upload rate.
  #   - All peers that have a better upload rate than Downloaders and are not interested are unchoked. 
  #   - One random peer that is unchoked. If it is interested, it is one of the 4 downloaders. 
  #       When choosing this random peer, peers that have connected in the last N seconds should be 3 times more 
  #       likely to be chosen. This peer only changes every 30 seconds.

  # Step 1: Pick the optimistic unchoke peer 

  selectOptimisticPeer(classifiedPeers)

  # Step 2: Update the downloaders to be the interested peers with the best upload rate.

  if classifiedPeers.interestedPeers.size > 0
    bestUploadInterested = classifiedPeers.interestedPeers.sort{ |a,b| b.uploadRate.value <=> a.uploadRate.value}.first(@targetUnchokedPeerCount)

    # If the optimistic unchoke peer is interested, he counts as a downloader.
    if @optimisticUnchokePeer && @optimisticUnchokePeer.peerInterested
      peerAlreadyIsDownloader = false
      bestUploadInterested.each do |peer|
        if peer.eql?(@optimisticUnchokePeer)
          peerAlreadyIsDownloader = true
          break
        end
      end
      bestUploadInterested[bestUploadInterested.size-1] = @optimisticUnchokePeer if ! peerAlreadyIsDownloader
    end

    # If one of the downloaders has changed, choke the peer
    downloadersMap = {}
    @downloaders.each{ |d| downloadersMap[d.trackerPeer] = d }
    bestUploadInterested.each do |peer|
      if downloadersMap.delete peer.trackerPeer
        # This peer was already a downloader. No changes.
      else
        # This peer wasn't a downloader before. Now it is; unchoke it
        result.unchoke.push peer if peer.peerChoked
      end
    end
    # Any peers remaining in the map are no longer a downloader. Choke them.
    result.choke = result.choke.concat(downloadersMap.values)

    @downloaders = bestUploadInterested
  end

  # Step 3: Unchoke all peers that have a better upload rate but are not interested.
  #         However, if we just started up, only unchoke targetUnchokedPeerCount peers.
  if @downloaders.size > 0
    if classifiedPeers.uninterestedPeers.size > 0
      classifiedPeers.uninterestedPeers.each do |peer|
        if peer.uploadRate.value > @downloaders[0].uploadRate.value && peer.peerChoked
          result.unchoke.push peer
        end
        if peer.uploadRate.value < @downloaders[0].uploadRate.value && ! peer.peerChoked && ! peer.eql?(@optimisticUnchokePeer)
          result.choke.push peer
        end
      end
    end
  else
    # No downloaders yet, so we can't tell who is fast or not. Unchoke some
    result.unchoke = result.unchoke.concat(classifiedPeers.uninterestedPeers.first(@targetUnchokedPeerCount))
  end

  @logger.debug "Manage peers result: #{result}"

  result
end