class Upload: def __init__(self, connection, choker, storage, max_slice_length, max_rate_period, fudge): self.connection = connection self.choker = choker self.storage = storage self.max_slice_length = max_slice_length self.max_rate_period = max_rate_period self.choked = True self.interested = False self.buffer = [] self.measure = Measure(max_rate_period, fudge) if storage.do_I_have_anything(): connection.send_bitfield(storage.get_have_list()) def send_have(self): if self.storage.get_amount_left() == 0 and self.supseed.Enabled(): want = self.connection.download._want piece = self.supseed.Next(want) if piece != None: self.connection.send_have(piece) def got_have(self, index): self.send_have() def got_not_interested(self): if self.interested: self.interested = False del self.buffer[:] self.choker.not_interested(self.connection) def got_interested(self): if not self.interested: self.interested = True self.choker.interested(self.connection) def flushed(self): while len(self.buffer) > 0 and self.connection.is_flushed(): index, begin, length = self.buffer[0] del self.buffer[0] piece = self.storage.get_piece(index, begin, length) if piece is None: self.connection.close() return self.measure.update_rate(len(piece)) self.connection.send_piece(index, begin, piece) def got_request(self, index, begin, length): if not self.interested or length > self.max_slice_length: self.connection.close() return if not self.choked: self.buffer.append((index, begin, length)) self.flushed() def got_cancel(self, index, begin, length): try: self.buffer.remove((index, begin, length)) except ValueError: pass def choke(self): if not self.choked: self.choked = True del self.buffer[:] self.connection.send_choke() def unchoke(self): if self.choked: self.choked = False self.connection.send_unchoke() def is_choked(self): return self.choked def is_interested(self): return self.interested def has_queries(self): return len(self.buffer) > 0 def get_rate(self): return self.measure.get_rate()
class SingleDownload: def __init__(self, downloader, connection): self.downloader = downloader self.connection = connection self.choked = True self.interested = False self.active_requests = [] self.measure = Measure(downloader.max_rate_period) self.have = Bitfield(downloader.numpieces) self.last = 0 self.example_interest = None def disconnected(self): self.downloader.downloads.remove(self) for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.lost_have(i) self._letgo() def _letgo(self): if not self.active_requests: return if self.downloader.storage.is_endgame(): self.active_requests = [] return lost = [] for index, begin, length in self.active_requests: self.downloader.storage.request_lost(index, begin, length) if index not in lost: lost.append(index) self.active_requests = [] ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more(lost) for d in self.downloader.downloads: if d.choked and not d.interested: for l in lost: if d.have[l] and self.downloader.storage.do_I_have_requests( l): d.interested = True d.connection.send_interested() break def got_choke(self): if not self.choked: self.choked = True self._letgo() def got_unchoke(self): if self.choked: self.choked = False if self.interested: self._request_more() def is_choked(self): return self.choked def is_interested(self): return self.interested def got_piece(self, index, begin, piece): try: self.active_requests.remove((index, begin, len(piece))) except ValueError: return False if self.downloader.storage.is_endgame(): self.downloader.all_requests.remove((index, begin, len(piece))) self.last = time() self.measure.update_rate(len(piece)) self.downloader.measurefunc(len(piece)) self.downloader.downmeasure.update_rate(len(piece)) if not self.downloader.storage.piece_came_in(index, begin, piece): if self.downloader.storage.is_endgame(): while self.downloader.storage.do_I_have_requests(index): nb, nl = self.downloader.storage.new_request(index) self.downloader.all_requests.append((index, nb, nl)) for d in self.downloader.downloads: d.fix_download_endgame() return False self.downloader.picker.bump(index) ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more([index]) return False if self.downloader.storage.do_I_have(index): self.downloader.picker.complete(index) if self.downloader.storage.is_endgame(): for d in self.downloader.downloads: if d is not self and d.interested: if d.choked: d.fix_download_endgame() else: try: d.active_requests.remove( (index, begin, len(piece))) except ValueError: continue d.connection.send_cancel(index, begin, len(piece)) d.fix_download_endgame() self._request_more() if self.downloader.picker.am_I_complete(): for d in [ i for i in self.downloader.downloads if i.have.numfalse == 0 ]: d.connection.close() return self.downloader.storage.do_I_have(index) def _want(self, index): return self.have[index] and self.downloader.storage.do_I_have_requests( index) def _request_more(self, indices=None): assert not self.choked if len(self.active_requests) == self.downloader.backlog: return if self.downloader.storage.is_endgame(): self.fix_download_endgame() return lost_interests = [] while len(self.active_requests) < self.downloader.backlog: if indices is None: interest = self.downloader.picker.next(self._want, self.have.numfalse == 0) else: interest = None for i in indices: if self.have[ i] and self.downloader.storage.do_I_have_requests( i): interest = i break if interest is None: break if not self.interested: self.interested = True self.connection.send_interested() self.example_interest = interest begin, length = self.downloader.storage.new_request(interest) self.downloader.picker.requested(interest, self.have.numfalse == 0) self.active_requests.append((interest, begin, length)) self.connection.send_request(interest, begin, length) if not self.downloader.storage.do_I_have_requests(interest): lost_interests.append(interest) if not self.active_requests and self.interested: self.interested = False self.connection.send_not_interested() if lost_interests: for d in self.downloader.downloads: if d.active_requests or not d.interested: continue if d.example_interest is not None and self.downloader.storage.do_I_have_requests( d.example_interest): continue for lost in lost_interests: if d.have[lost]: break else: continue interest = self.downloader.picker.next(d._want, d.have.numfalse == 0) if interest is None: d.interested = False d.connection.send_not_interested() else: d.example_interest = interest if self.downloader.storage.is_endgame(): self.downloader.all_requests = [] for d in self.downloader.downloads: self.downloader.all_requests.extend(d.active_requests) for d in self.downloader.downloads: d.fix_download_endgame() def fix_download_endgame(self): want = [ a for a in self.downloader.all_requests if self.have[a[0]] and a not in self.active_requests ] if self.interested and not self.active_requests and not want: self.interested = False self.connection.send_not_interested() return if not self.interested and want: self.interested = True self.connection.send_interested() if self.choked: return shuffle(want) del want[self.downloader.backlog - len(self.active_requests):] self.active_requests.extend(want) for piece, begin, length in want: self.connection.send_request(piece, begin, length) def got_have(self, index): if self.have[index]: return self.have[index] = True self.downloader.picker.got_have(index) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: self.connection.close() return if self.downloader.storage.is_endgame(): self.fix_download_endgame() elif self.downloader.storage.do_I_have_requests(index): if not self.choked: self._request_more([index]) else: if not self.interested: self.interested = True self.connection.send_interested() def got_have_bitfield(self, have): self.have = have for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.got_have(i) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: self.connection.close() return if self.downloader.storage.is_endgame(): for piece, begin, length in self.downloader.all_requests: if self.have[piece]: self.interested = True self.connection.send_interested() return for i in xrange(len(self.have)): if self.have[i] and self.downloader.storage.do_I_have_requests(i): self.interested = True self.connection.send_interested() return def get_rate(self): return self.measure.get_rate() def is_snubbed(self): return time() - self.last > self.downloader.snub_time
class RateLimiter: def __init__(self, sched, unitsize, slotsfunc=lambda x: None): self.sched = sched self.last = None self.unitsize = unitsize self.slotsfunc = slotsfunc self.measure = Measure(MAX_RATE_PERIOD) self.autoadjust = False self.upload_rate = MAX_RATE * 1000 self.slots = SLOTS_STARTING # garbage if not automatic def set_upload_rate(self, rate): # rate = -1 # test automatic if rate < 0: if self.autoadjust: return self.autoadjust = True self.autoadjustup = 0 self.pings = [] rate = MAX_RATE self.slots = SLOTS_STARTING self.slotsfunc(self.slots) else: self.autoadjust = False if not rate: rate = MAX_RATE self.upload_rate = rate * 1000 self.lasttime = clock() self.bytes_sent = 0 def queue(self, conn): assert conn.next_upload is None if self.last is None: self.last = conn conn.next_upload = conn self.try_send(True) else: conn.next_upload = self.last.next_upload self.last.next_upload = conn self.last = conn def try_send(self, check_time=False): t = clock() self.bytes_sent -= (t - self.lasttime) * self.upload_rate self.lasttime = t if check_time: self.bytes_sent = max(self.bytes_sent, 0) cur = self.last.next_upload while self.bytes_sent <= 0: bytes = cur.send_partial(self.unitsize) self.bytes_sent += bytes self.measure.update_rate(bytes) if bytes == 0 or cur.backlogged(): if self.last is cur: self.last = None cur.next_upload = None break else: self.last.next_upload = cur.next_upload cur.next_upload = None cur = self.last.next_upload else: self.last = cur cur = cur.next_upload else: self.sched(self.try_send, self.bytes_sent / self.upload_rate) def adjust_sent(self, bytes): self.bytes_sent = min(self.bytes_sent + bytes, self.upload_rate * 3) self.measure.update_rate(bytes) def ping(self, delay): if DEBUG: print delay if not self.autoadjust: return self.pings.append(delay > PING_BOUNDARY) if len(self.pings) < PING_SAMPLES + PING_DISCARDS: return if DEBUG: print 'cycle' pings = sum(self.pings[PING_DISCARDS:]) del self.pings[:] if pings >= PING_THRESHHOLD: # assume flooded if self.upload_rate == MAX_RATE: self.upload_rate = self.measure.get_rate() * ADJUST_DOWN else: self.upload_rate = min(self.upload_rate, self.measure.get_rate() * 1.1) self.upload_rate = max(int(self.upload_rate * ADJUST_DOWN), 2) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print 'adjust down to ' + str(self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_FIRST else: # not flooded if self.upload_rate == MAX_RATE: return self.autoadjustup -= 1 if self.autoadjustup: return self.upload_rate = int(self.upload_rate * ADJUST_UP) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print 'adjust up to ' + str(self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_NEXT
class Upload: def __init__(self, connection, choker, storage, max_slice_length, max_rate_period, fudge): self.connection = connection self.choker = choker self.storage = storage self.max_slice_length = max_slice_length self.max_rate_period = max_rate_period self.choked = True self.interested = False self.buffer = [] self.measure = Measure(max_rate_period, fudge) if storage.do_I_have_anything(): connection.send_bitfield(storage.get_have_list()) def got_not_interested(self): if self.interested: self.interested = False del self.buffer[:] self.choker.not_interested(self.connection) def got_interested(self): if not self.interested: self.interested = True self.choker.interested(self.connection) def flushed(self): while len(self.buffer) > 0 and self.connection.is_flushed(): index, begin, length = self.buffer[0] del self.buffer[0] piece = self.storage.get_piece(index, begin, length) if piece is None: self.connection.close() return self.measure.update_rate(len(piece)) self.connection.send_piece(index, begin, piece) def got_request(self, index, begin, length): if not self.interested or length > self.max_slice_length: self.connection.close() return if not self.choked: self.buffer.append((index, begin, length)) self.flushed() def got_cancel(self, index, begin, length): try: self.buffer.remove((index, begin, length)) except ValueError: pass def choke(self): if not self.choked: self.choked = True del self.buffer[:] self.connection.send_choke() def unchoke(self): if self.choked: self.choked = False self.connection.send_unchoke() def is_choked(self): return self.choked def is_interested(self): return self.interested def has_queries(self): return len(self.buffer) > 0 def get_rate(self): return self.measure.get_rate()
class Upload: def __init__(self, connection, choker, storage, max_slice_length, max_rate_period, fudge): # sock connection self.connection = connection self.choker = choker self.storage = storage # 最大slice self.max_slice_length = max_slice_length # 最大速率间距 self.max_rate_period = max_rate_period # local choke remote self.choked = True # remote not interested local self.interested = False # 缓存 self.buffer = [] # 测速对象 self.measure = Measure(max_rate_period, fudge) # 如果存储了数据,那么发送bitfield if storage.do_I_have_anything(): connection.send_bitfield(storage.get_have_list()) # 处理收到的不感兴趣消息 def got_not_interested(self): if self.interested: self.interested = False # 删除缓存数据 del self.buffer[:] # remote 对Local不感兴趣,重新rechoke self.choker.not_interested(self.connection) # 处理收到的感兴趣的消息 def got_interested(self): if not self.interested: self.interested = True # remote is interested local, and rechoke self.choker.interested(self.connection) # flushed buffer and send message def flushed(self): # if the buffer len great zero and connection buffer is zero while len(self.buffer) > 0 and self.connection.is_flushed(): index, begin, length = self.buffer[0] del self.buffer[0] # read piece from disk piece = self.storage.get_piece(index, begin, length) # if piece is not readed, so close connection if piece is None: self.connection.close() return # updat rate self.measure.update_rate(len(piece)) # send piece self.connection.send_piece(index, begin, piece) # 处理收到的请求消息 def got_request(self, index, begin, length): #如果remote is not interested local or request len gt max slice length (16K) # so close conn if not self.interested or length > self.max_slice_length: self.connection.close() return # if local choked remote # add request to buffer, and flushed buffer if not self.choked: self.buffer.append((index, begin, length)) self.flushed() # got cancel message received def got_cancel(self, index, begin, length): try: # remove request message from buffer self.buffer.remove((index, begin, length)) except ValueError: pass # send choke def choke(self): # if local unchoked remote # and choke it if not self.choked: self.choked = True # clean buffer del self.buffer[:] # send choke to remoate self.connection.send_choke() # send unchoke def unchoke(self): if self.choked: self.choked = False self.connection.send_unchoke() # local is choked remote def is_choked(self): return self.choked # remote is interested local def is_interested(self): return self.interested # has data or not in buffer def has_queries(self): return len(self.buffer) > 0 # get update rate def get_rate(self): return self.measure.get_rate()
class RateLimiter: def __init__(self, sched, unitsize, slotsfunc=lambda x: None): self.sched = sched self.last = None self.unitsize = unitsize self.slotsfunc = slotsfunc self.measure = Measure(MAX_RATE_PERIOD) self.autoadjust = False self.upload_rate = MAX_RATE * 1000 self.slots = SLOTS_STARTING def set_upload_rate(self, rate): if DEBUG: print >> sys.stderr, 'RateLimiter: set_upload_rate', rate if rate < 0: if self.autoadjust: return self.autoadjust = True self.autoadjustup = 0 self.pings = [] rate = MAX_RATE self.slots = SLOTS_STARTING self.slotsfunc(self.slots) else: self.autoadjust = False if not rate: rate = MAX_RATE self.upload_rate = rate * 1000 self.lasttime = clock() self.bytes_sent = 0 def queue(self, conn): if DEBUG: print >> sys.stderr, 'RateLimiter: queue', conn if self.last is None: self.last = conn conn.next_upload = conn self.try_send(True) else: conn.next_upload = self.last.next_upload self.last.next_upload = conn if not conn.connection.is_coordinator_con(): self.last = conn def try_send(self, check_time=False): if DEBUG: print >> sys.stderr, 'RateLimiter: try_send' t = clock() self.bytes_sent -= (t - self.lasttime) * self.upload_rate self.lasttime = t if check_time: self.bytes_sent = max(self.bytes_sent, 0) cur = self.last.next_upload while self.bytes_sent <= 0: bytes = cur.send_partial(self.unitsize) self.bytes_sent += bytes self.measure.update_rate(bytes) if bytes == 0 or cur.backlogged(): if self.last is cur: self.last = None cur.next_upload = None break else: self.last.next_upload = cur.next_upload cur.next_upload = None cur = self.last.next_upload elif not cur.connection.is_coordinator_con( ) or not cur.upload.buffer: self.last = cur cur = cur.next_upload else: delay = min(5.0, self.bytes_sent / self.upload_rate) self.sched(self.try_send, delay) def adjust_sent(self, bytes): self.bytes_sent = min(self.bytes_sent + bytes, self.upload_rate * 3) self.measure.update_rate(bytes) def ping(self, delay): if DEBUG: print >> sys.stderr, delay if not self.autoadjust: return self.pings.append(delay > PING_BOUNDARY) if len(self.pings) < PING_SAMPLES + PING_DISCARDS: return if DEBUG: print >> sys.stderr, 'RateLimiter: cycle' pings = sum(self.pings[PING_DISCARDS:]) del self.pings[:] if pings >= PING_THRESHHOLD: if self.upload_rate == MAX_RATE: self.upload_rate = self.measure.get_rate() * ADJUST_DOWN else: self.upload_rate = min(self.upload_rate, self.measure.get_rate() * 1.1) self.upload_rate = max(int(self.upload_rate * ADJUST_DOWN), 2) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print >> sys.stderr, 'RateLimiter: adjust down to ' + str( self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_FIRST else: if self.upload_rate == MAX_RATE: return self.autoadjustup -= 1 if self.autoadjustup: return self.upload_rate = int(self.upload_rate * ADJUST_UP) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print >> sys.stderr, 'RateLimiter: adjust up to ' + str( self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_NEXT
class RateLimiter: def __init__(self, sched, unitsize, slotsfunc=lambda x: None): self.sched = sched self.last = None self.unitsize = unitsize self.slotsfunc = slotsfunc self.measure = Measure(MAX_RATE_PERIOD) self.autoadjust = False self.upload_rate = MAX_RATE * 1000 self.slots = SLOTS_STARTING # garbage if not automatic def set_upload_rate(self, rate): # rate = -1 # test automatic if rate < 0: if self.autoadjust: return self.autoadjust = True self.autoadjustup = 0 self.pings = [] rate = MAX_RATE self.slots = SLOTS_STARTING self.slotsfunc(self.slots) else: self.autoadjust = False if not rate: rate = MAX_RATE self.upload_rate = rate * 1000 self.lasttime = clock() self.bytes_sent = 0 def queue(self, conn): assert conn.next_upload is None if self.last is None: self.last = conn conn.next_upload = conn self.try_send(True) else: conn.next_upload = self.last.next_upload self.last.next_upload = conn # 2fastbt_ if not conn.connection.is_coordinator_con(): self.last = conn # _2fastbt def try_send(self, check_time=False): t = clock() self.bytes_sent -= (t - self.lasttime) * self.upload_rate #print 'try_send: bytes_sent: %s' % self.bytes_sent self.lasttime = t if check_time: self.bytes_sent = max(self.bytes_sent, 0) cur = self.last.next_upload while self.bytes_sent <= 0: bytes = cur.send_partial(self.unitsize) self.bytes_sent += bytes self.measure.update_rate(bytes) if bytes == 0 or cur.backlogged(): if self.last is cur: self.last = None cur.next_upload = None break else: self.last.next_upload = cur.next_upload cur.next_upload = None cur = self.last.next_upload else: # 2fastbt_ if not cur.connection.is_coordinator_con( ) or not cur.upload.buffer: # _2fastbt self.last = cur cur = cur.next_upload # 2fastbt_ else: pass # _2fastbt else: self.sched(self.try_send, self.bytes_sent / self.upload_rate) def adjust_sent(self, bytes): self.bytes_sent = min(self.bytes_sent + bytes, self.upload_rate * 3) self.measure.update_rate(bytes) def ping(self, delay): ##raise Exception('Is this called?') if DEBUG: print delay if not self.autoadjust: return self.pings.append(delay > PING_BOUNDARY) if len(self.pings) < PING_SAMPLES + PING_DISCARDS: return if DEBUG: print 'cycle' pings = sum(self.pings[PING_DISCARDS:]) del self.pings[:] if pings >= PING_THRESHHOLD: # assume flooded if self.upload_rate == MAX_RATE: self.upload_rate = self.measure.get_rate() * ADJUST_DOWN else: self.upload_rate = min(self.upload_rate, self.measure.get_rate() * 1.1) self.upload_rate = max(int(self.upload_rate * ADJUST_DOWN), 2) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print 'adjust down to ' + str(self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_FIRST else: # not flooded if self.upload_rate == MAX_RATE: return self.autoadjustup -= 1 if self.autoadjustup: return self.upload_rate = int(self.upload_rate * ADJUST_UP) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print 'adjust up to ' + str(self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_NEXT
class SingleDownload: def __init__(self, downloader, connection): self.downloader = downloader self.connection = connection # Whether the peer is choking this client. self.choked = True # Whether this client is interested in data the peer has. self.interested = False # The (index, begin, length) tuples this client has requested from the peer. self.active_requests = [] # Measures the download rate from the peer. self.measure = Measure(downloader.max_rate_period) # The pieces the peer has. self.have = Bitfield(downloader.numpieces) # The last time this client has gotten data from the peer. self.last = 0 self.example_interest = None def disconnected(self): self.downloader.downloads.remove(self) # Decrement the availability of each piece this peer had. for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.lost_have(i) self._letgo() def _letgo(self): if not self.active_requests: return if self.downloader.storage.is_endgame(): # If in endgame mode, requesting blocks in active_requests from other peers anyway. self.active_requests = [] return # The piece indexes that this client was requesting from the peer. lost = [] for index, begin, length in self.active_requests: # No longer downloading this block. self.downloader.storage.request_lost(index, begin, length) if index not in lost: lost.append(index) self.active_requests = [] # Get all other SingleDownload instances that are not choking us. ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more(lost) for d in self.downloader.downloads: # Get all other SingleDownload instances that are choking us. None of these were in ds. # Also, filter by the ones that we are not interested in. if d.choked and not d.interested: for l in lost: if d.have[l] and self.downloader.storage.do_I_have_requests(l): # This other peer has a piece that the client was downloading from this peer, # so become interested. d.interested = True d.connection.send_interested() break def got_choke(self): if not self.choked: # The peer choked this client. self.choked = True self._letgo() def got_unchoke(self): if self.choked: # The peer unchoked this client. self.choked = False if self.interested: # This peer has data we want, so request it. self._request_more() def is_choked(self): return self.choked def is_interested(self): return self.interested def got_piece(self, index, begin, piece): try: # This active request to the peer has been fulfilled. self.active_requests.remove((index, begin, len(piece))) except ValueError: return False if self.downloader.storage.is_endgame(): # Remove this from the consolidated list of blocks sent to all peers. self.downloader.all_requests.remove((index, begin, len(piece))) # Update our upload and download rates. self.last = time() self.measure.update_rate(len(piece)) self.downloader.measurefunc(len(piece)) self.downloader.downmeasure.update_rate(len(piece)) # TODO if not self.downloader.storage.piece_came_in(index, begin, piece): # This block completed a piece but it failed validation. if self.downloader.storage.is_endgame(): while self.downloader.storage.do_I_have_requests(index): nb, nl = self.downloader.storage.new_request(index) self.downloader.all_requests.append((index, nb, nl)) for d in self.downloader.downloads: d.fix_download_endgame() return False # Decrease the priority of this piece... self.downloader.picker.bump(index) # ... but try downloading this piece again immediately? ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more([index]) return False if self.downloader.storage.do_I_have(index): # Notify the picker that this piece is complete. self.downloader.picker.complete(index) if self.downloader.storage.is_endgame(): for d in self.downloader.downloads: if d is not self and d.interested: if d.choked: # Keep requesting pieces that we're requesting from other peers. d.fix_download_endgame() else: # Cancel the request for this block from this peer. try: d.active_requests.remove((index, begin, len(piece))) except ValueError: # Wasn't requesting this block from this peer. continue d.connection.send_cancel(index, begin, len(piece)) # Keep requesting pieces that we're requesting from other peers. d.fix_download_endgame() self._request_more() if self.downloader.picker.am_I_complete(): for d in [i for i in self.downloader.downloads if i.have.numfalse == 0]: d.connection.close() return self.downloader.storage.do_I_have(index) def _want(self, index): # Want a piece if this user has it and TODO. return self.have[index] and self.downloader.storage.do_I_have_requests(index) def _request_more(self, indices = None): assert not self.choked # Return if we already have the maximum outstanding requests to this peer. if len(self.active_requests) == self.downloader.backlog: return if self.downloader.storage.is_endgame(): # Keep requesting pieces that we're requesting from other peers. self.fix_download_endgame() return lost_interests = [] while len(self.active_requests) < self.downloader.backlog: # Have less than the maximum outstanding requests to this peer... if indices is None: # Not passed any specific indexes to get. Pick a piece to download. interest = self.downloader.picker.next(self._want, self.have.numfalse == 0) else: # Pick a piece from one of the given indexes to download. interest = None for i in indices: if self.have[i] and self.downloader.storage.do_I_have_requests(i): interest = i break if interest is None: # Could not find anything we want, so break. break if not self.interested: # Found a piece we want; tell the peer that this client is interested. self.interested = True self.connection.send_interested() self.example_interest = interest # Get a block of the piece to request. begin, length = self.downloader.storage.new_request(interest) # Notify the PiecePicker that we're requesting this piece. self.downloader.picker.requested(interest, self.have.numfalse == 0) # Append to the list of all requests, and actually request it. self.active_requests.append((interest, begin, length)) self.connection.send_request(interest, begin, length) if not self.downloader.storage.do_I_have_requests(interest): # TODO lost_interests.append(interest) if not self.active_requests and self.interested: # Peer has no pieces this client wants, so no longer interested. self.interested = False self.connection.send_not_interested() if lost_interests: for d in self.downloader.downloads: if d.active_requests or not d.interested: # Looking for a client that has no active requests, but we're interested in. continue if d.example_interest is not None and self.downloader.storage.do_I_have_requests(d.example_interest): continue for lost in lost_interests: if d.have[lost]: break else: continue # interest = self.downloader.picker.next(d._want, d.have.numfalse == 0) if interest is None: d.interested = False d.connection.send_not_interested() else: d.example_interest = interest if self.downloader.storage.is_endgame(): # Now entering endgame mode. self.downloader.all_requests = [] # Consolidate the block requests this client has sent to all peers. for d in self.downloader.downloads: self.downloader.all_requests.extend(d.active_requests) # Request from each peer pieces that we're requesting from other peers. for d in self.downloader.downloads: d.fix_download_endgame() def fix_download_endgame(self): # Find pieces this peer has which we're requesting from other peers. want = [a for a in self.downloader.all_requests if self.have[a[0]] and a not in self.active_requests] if self.interested and not self.active_requests and not want: # There are no such pieces, and we're not requesting any others, so become uninterested. self.interested = False self.connection.send_not_interested() return if not self.interested and want: # There are such pieces, so become interested. self.interested = True self.connection.send_interested() if self.choked: # Peer is choking us, so we can't send any requests yet. return # Don't send exceed the maximum number of requests to the client. shuffle(want) del want[self.downloader.backlog - len(self.active_requests):] # Request the blocks that we're requesting from other peers. self.active_requests.extend(want) for piece, begin, length in want: self.connection.send_request(piece, begin, length) def got_have(self, index): if self.have[index]: # Already knew that the client has this piece. return self.have[index] = True # Increase the availability of this piece. self.downloader.picker.got_have(index) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: # Both this client and the peer have every piece, so close. self.connection.close() return if self.downloader.storage.is_endgame(): # Keep requesting pieces that we're requesting from other peers. self.fix_download_endgame() elif self.downloader.storage.do_I_have_requests(index): if not self.choked: self._request_more([index]) else: # The peer is choking us, but express that this client is now interested. if not self.interested: self.interested = True self.connection.send_interested() def got_have_bitfield(self, have): # Assign the full bitfield of pieces this client has. self.have = have for i in xrange(len(self.have)): # Increase the availability of each piece. if self.have[i]: self.downloader.picker.got_have(i) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: # Both this client and the peer have every piece, so close. self.connection.close() return if self.downloader.storage.is_endgame(): for piece, begin, length in self.downloader.all_requests: # Endgame, so want to send a request for any missing blocks to this peer. if self.have[piece]: self.interested = True self.connection.send_interested() return for i in xrange(len(self.have)): # This peer has a piece that we want, so express interest. if self.have[i] and self.downloader.storage.do_I_have_requests(i): self.interested = True self.connection.send_interested() return def get_rate(self): return self.measure.get_rate() def is_snubbed(self): # Whether significant time has gone by without getting data from a peer. return time() - self.last > self.downloader.snub_time
class SingleDownload: def __init__(self, downloader, connection): self.downloader = downloader self.connection = connection # Whether the peer is choking this client. self.choked = True # Whether this client is interested in data the peer has. self.interested = False # The (index, begin, length) tuples this client has requested from the peer. self.active_requests = [] # Measures the download rate from the peer. self.measure = Measure(downloader.max_rate_period) # The pieces the peer has. self.have = Bitfield(downloader.numpieces) # The last time this client has gotten data from the peer. self.last = 0 self.example_interest = None def disconnected(self): self.downloader.downloads.remove(self) # Decrement the availability of each piece this peer had. for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.lost_have(i) self._letgo() def _letgo(self): if not self.active_requests: return if self.downloader.storage.is_endgame(): # If in endgame mode, requesting blocks in active_requests from other peers anyway. self.active_requests = [] return # The piece indexes that this client was requesting from the peer. lost = [] for index, begin, length in self.active_requests: # No longer downloading this block. self.downloader.storage.request_lost(index, begin, length) if index not in lost: lost.append(index) self.active_requests = [] # Get all other SingleDownload instances that are not choking us. ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more(lost) for d in self.downloader.downloads: # Get all other SingleDownload instances that are choking us. None of these were in ds. # Also, filter by the ones that we are not interested in. if d.choked and not d.interested: for l in lost: if d.have[l] and self.downloader.storage.do_I_have_requests( l): # This other peer has a piece that the client was downloading from this peer, # so become interested. d.interested = True d.connection.send_interested() break def got_choke(self): if not self.choked: # The peer choked this client. self.choked = True self._letgo() def got_unchoke(self): if self.choked: # The peer unchoked this client. self.choked = False if self.interested: # This peer has data we want, so request it. self._request_more() def is_choked(self): return self.choked def is_interested(self): return self.interested def got_piece(self, index, begin, piece): try: # This active request to the peer has been fulfilled. self.active_requests.remove((index, begin, len(piece))) except ValueError: return False if self.downloader.storage.is_endgame(): # Remove this from the consolidated list of blocks sent to all peers. self.downloader.all_requests.remove((index, begin, len(piece))) # Update our upload and download rates. self.last = time() self.measure.update_rate(len(piece)) self.downloader.measurefunc(len(piece)) self.downloader.downmeasure.update_rate(len(piece)) # TODO if not self.downloader.storage.piece_came_in(index, begin, piece): # This block completed a piece but it failed validation. if self.downloader.storage.is_endgame(): while self.downloader.storage.do_I_have_requests(index): nb, nl = self.downloader.storage.new_request(index) self.downloader.all_requests.append((index, nb, nl)) for d in self.downloader.downloads: d.fix_download_endgame() return False # Decrease the priority of this piece... self.downloader.picker.bump(index) # ... but try downloading this piece again immediately? ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more([index]) return False if self.downloader.storage.do_I_have(index): # Notify the picker that this piece is complete. self.downloader.picker.complete(index) if self.downloader.storage.is_endgame(): for d in self.downloader.downloads: if d is not self and d.interested: if d.choked: # Keep requesting pieces that we're requesting from other peers. d.fix_download_endgame() else: # Cancel the request for this block from this peer. try: d.active_requests.remove( (index, begin, len(piece))) except ValueError: # Wasn't requesting this block from this peer. continue d.connection.send_cancel(index, begin, len(piece)) # Keep requesting pieces that we're requesting from other peers. d.fix_download_endgame() self._request_more() if self.downloader.picker.am_I_complete(): for d in [ i for i in self.downloader.downloads if i.have.numfalse == 0 ]: d.connection.close() return self.downloader.storage.do_I_have(index) def _want(self, index): # Want a piece if this user has it and TODO. return self.have[index] and self.downloader.storage.do_I_have_requests( index) def _request_more(self, indices=None): assert not self.choked # Return if we already have the maximum outstanding requests to this peer. if len(self.active_requests) == self.downloader.backlog: return if self.downloader.storage.is_endgame(): # Keep requesting pieces that we're requesting from other peers. self.fix_download_endgame() return lost_interests = [] while len(self.active_requests) < self.downloader.backlog: # Have less than the maximum outstanding requests to this peer... if indices is None: # Not passed any specific indexes to get. Pick a piece to download. interest = self.downloader.picker.next(self._want, self.have.numfalse == 0) else: # Pick a piece from one of the given indexes to download. interest = None for i in indices: if self.have[ i] and self.downloader.storage.do_I_have_requests( i): interest = i break if interest is None: # Could not find anything we want, so break. break if not self.interested: # Found a piece we want; tell the peer that this client is interested. self.interested = True self.connection.send_interested() self.example_interest = interest # Get a block of the piece to request. begin, length = self.downloader.storage.new_request(interest) # Notify the PiecePicker that we're requesting this piece. self.downloader.picker.requested(interest, self.have.numfalse == 0) # Append to the list of all requests, and actually request it. self.active_requests.append((interest, begin, length)) self.connection.send_request(interest, begin, length) if not self.downloader.storage.do_I_have_requests(interest): # TODO lost_interests.append(interest) if not self.active_requests and self.interested: # Peer has no pieces this client wants, so no longer interested. self.interested = False self.connection.send_not_interested() if lost_interests: for d in self.downloader.downloads: if d.active_requests or not d.interested: # Looking for a client that has no active requests, but we're interested in. continue if d.example_interest is not None and self.downloader.storage.do_I_have_requests( d.example_interest): continue for lost in lost_interests: if d.have[lost]: break else: continue # interest = self.downloader.picker.next(d._want, d.have.numfalse == 0) if interest is None: d.interested = False d.connection.send_not_interested() else: d.example_interest = interest if self.downloader.storage.is_endgame(): # Now entering endgame mode. self.downloader.all_requests = [] # Consolidate the block requests this client has sent to all peers. for d in self.downloader.downloads: self.downloader.all_requests.extend(d.active_requests) # Request from each peer pieces that we're requesting from other peers. for d in self.downloader.downloads: d.fix_download_endgame() def fix_download_endgame(self): # Find pieces this peer has which we're requesting from other peers. want = [ a for a in self.downloader.all_requests if self.have[a[0]] and a not in self.active_requests ] if self.interested and not self.active_requests and not want: # There are no such pieces, and we're not requesting any others, so become uninterested. self.interested = False self.connection.send_not_interested() return if not self.interested and want: # There are such pieces, so become interested. self.interested = True self.connection.send_interested() if self.choked: # Peer is choking us, so we can't send any requests yet. return # Don't send exceed the maximum number of requests to the client. shuffle(want) del want[self.downloader.backlog - len(self.active_requests):] # Request the blocks that we're requesting from other peers. self.active_requests.extend(want) for piece, begin, length in want: self.connection.send_request(piece, begin, length) def got_have(self, index): if self.have[index]: # Already knew that the client has this piece. return self.have[index] = True # Increase the availability of this piece. self.downloader.picker.got_have(index) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: # Both this client and the peer have every piece, so close. self.connection.close() return if self.downloader.storage.is_endgame(): # Keep requesting pieces that we're requesting from other peers. self.fix_download_endgame() elif self.downloader.storage.do_I_have_requests(index): if not self.choked: self._request_more([index]) else: # The peer is choking us, but express that this client is now interested. if not self.interested: self.interested = True self.connection.send_interested() def got_have_bitfield(self, have): # Assign the full bitfield of pieces this client has. self.have = have for i in xrange(len(self.have)): # Increase the availability of each piece. if self.have[i]: self.downloader.picker.got_have(i) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: # Both this client and the peer have every piece, so close. self.connection.close() return if self.downloader.storage.is_endgame(): for piece, begin, length in self.downloader.all_requests: # Endgame, so want to send a request for any missing blocks to this peer. if self.have[piece]: self.interested = True self.connection.send_interested() return for i in xrange(len(self.have)): # This peer has a piece that we want, so express interest. if self.have[i] and self.downloader.storage.do_I_have_requests(i): self.interested = True self.connection.send_interested() return def get_rate(self): return self.measure.get_rate() def is_snubbed(self): # Whether significant time has gone by without getting data from a peer. return time() - self.last > self.downloader.snub_time
class RateLimiter: def __init__(self, sched, unitsize, slotsfunc=lambda x: None): self.sched = sched self.last = None self.unitsize = unitsize self.slotsfunc = slotsfunc self.measure = Measure(MAX_RATE_PERIOD) self.autoadjust = False self.upload_rate = MAX_RATE * 1000 self.slots = SLOTS_STARTING # garbage if not automatic def set_upload_rate(self, rate): if DEBUG: print >> sys.stderr, "RateLimiter: set_upload_rate", rate # rate = -1 # test automatic if rate < 0: if self.autoadjust: return self.autoadjust = True self.autoadjustup = 0 self.pings = [] rate = MAX_RATE self.slots = SLOTS_STARTING self.slotsfunc(self.slots) else: self.autoadjust = False if not rate: rate = MAX_RATE self.upload_rate = rate * 1000 self.lasttime = clock() self.bytes_sent = 0 def queue(self, conn): if DEBUG: print >> sys.stderr, "RateLimiter: queue", conn assert conn.next_upload is None if self.last is None: self.last = conn conn.next_upload = conn self.try_send(True) else: conn.next_upload = self.last.next_upload self.last.next_upload = conn self.last = conn def try_send(self, check_time=False): if DEBUG: print >> sys.stderr, "RateLimiter: try_send" t = clock() self.bytes_sent -= (t - self.lasttime) * self.upload_rate #print >> sys.stderr, 'try_send: bytes_sent: %s' % self.bytes_sent self.lasttime = t if check_time and (self.upload_rate < MAX_RATE * 1000): #do not set self.bytes_sent to 0 if we are unlimited... self.bytes_sent = max(self.bytes_sent, 0) cur = self.last.next_upload while self.bytes_sent <= 0: #we would like to send up to self.bytes_sent data to someone #why not try to send this at once? #bytes = cur.send_partial(self.unitsize) remaining_bytes = max(self.unitsize, int(1 - self.bytes_sent)) bytes = cur.send_partial(remaining_bytes) self.bytes_sent += bytes self.measure.update_rate(bytes) if bytes == 0 or cur.backlogged(): if self.last is cur: self.last = None cur.next_upload = None break else: self.last.next_upload = cur.next_upload cur.next_upload = None cur = self.last.next_upload else: #does this connection still have a buffer? if not cur.upload.buffer: self.last = cur cur = cur.next_upload #switch to the next one else: pass else: # 01/04/10 Boudewijn: because we use a -very- small value # to indicate a 0bps rate, we will schedule the call to be # made in a very long time. This results in no upload for # a very long time. # # the try_send method has protection again calling to # soon, so we can simply schedule the call to be made # sooner. delay = min(5.0, self.bytes_sent / self.upload_rate) self.sched(self.try_send, delay) def adjust_sent(self, bytes): # if DEBUG: print >>sys.stderr, "RateLimiter: adjust_sent", bytes self.bytes_sent = min(self.bytes_sent + bytes, self.upload_rate * 3) self.measure.update_rate(bytes) def ping(self, delay): ##raise Exception('Is this called?') if DEBUG: print >> sys.stderr, delay if not self.autoadjust: return self.pings.append(delay > PING_BOUNDARY) if len(self.pings) < PING_SAMPLES + PING_DISCARDS: return if DEBUG: print >> sys.stderr, 'RateLimiter: cycle' pings = sum(self.pings[PING_DISCARDS:]) del self.pings[:] if pings >= PING_THRESHHOLD: # assume flooded if self.upload_rate == MAX_RATE: self.upload_rate = self.measure.get_rate() * ADJUST_DOWN else: self.upload_rate = min(self.upload_rate, self.measure.get_rate() * 1.1) self.upload_rate = max(int(self.upload_rate * ADJUST_DOWN), 2) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print >> sys.stderr, 'RateLimiter: adjust down to ' + str( self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_FIRST else: # not flooded if self.upload_rate == MAX_RATE: return self.autoadjustup -= 1 if self.autoadjustup: return self.upload_rate = int(self.upload_rate * ADJUST_UP) self.slots = int(sqrt(self.upload_rate * SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print >> sys.stderr, 'RateLimiter: adjust up to ' + str( self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_NEXT
class SingleDownload: def __init__(self, downloader, connection): self.downloader = downloader self.connection = connection # remote choked local self.choked = True # local is not interest remote self.interested = False # active request list self.active_requests = [] # measure down rate self.measure = Measure(downloader.max_rate_period) # bitfield self.have = Bitfield(downloader.numpieces) self.last = 0 self.example_interest = None # disconnected def disconnected(self): # remove self from downloads array self.downloader.downloads.remove(self) for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.lost_have(i) self._letgo() def _letgo(self): if not self.active_requests: return if self.downloader.storage.is_endgame(): self.active_requests = [] return lost = [] for index, begin, length in self.active_requests: self.downloader.storage.request_lost(index, begin, length) if index not in lost: lost.append(index) self.active_requests = [] ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more(lost) for d in self.downloader.downloads: if d.choked and not d.interested: for l in lost: if d.have[l] and self.downloader.storage.do_I_have_requests(l): d.interested = True d.connection.send_interested() break def got_choke(self): if not self.choked: self.choked = True self._letgo() def got_unchoke(self): if self.choked: self.choked = False if self.interested: self._request_more() def is_choked(self): return self.choked def is_interested(self): return self.interested def got_piece(self, index, begin, piece): try: self.active_requests.remove((index, begin, len(piece))) except ValueError: return False if self.downloader.storage.is_endgame(): self.downloader.all_requests.remove((index, begin, len(piece))) self.last = time() self.measure.update_rate(len(piece)) self.downloader.measurefunc(len(piece)) self.downloader.downmeasure.update_rate(len(piece)) if not self.downloader.storage.piece_came_in(index, begin, piece): if self.downloader.storage.is_endgame(): while self.downloader.storage.do_I_have_requests(index): nb, nl = self.downloader.storage.new_request(index) self.downloader.all_requests.append((index, nb, nl)) for d in self.downloader.downloads: d.fix_download_endgame() return False self.downloader.picker.bump(index) ds = [d for d in self.downloader.downloads if not d.choked] shuffle(ds) for d in ds: d._request_more([index]) return False if self.downloader.storage.do_I_have(index): self.downloader.picker.complete(index) if self.downloader.storage.is_endgame(): for d in self.downloader.downloads: if d is not self and d.interested: if d.choked: d.fix_download_endgame() else: try: d.active_requests.remove((index, begin, len(piece))) except ValueError: continue d.connection.send_cancel(index, begin, len(piece)) d.fix_download_endgame() self._request_more() if self.downloader.picker.am_I_complete(): for d in [i for i in self.downloader.downloads if i.have.numfalse == 0]: d.connection.close() return self.downloader.storage.do_I_have(index) def _want(self, index): return self.have[index] and self.downloader.storage.do_I_have_requests(index) def _request_more(self, indices = None): assert not self.choked if len(self.active_requests) == self.downloader.backlog: return if self.downloader.storage.is_endgame(): self.fix_download_endgame() return lost_interests = [] while len(self.active_requests) < self.downloader.backlog: if indices is None: interest = self.downloader.picker.next(self._want, self.have.numfalse == 0) else: interest = None for i in indices: if self.have[i] and self.downloader.storage.do_I_have_requests(i): interest = i break if interest is None: break if not self.interested: self.interested = True self.connection.send_interested() self.example_interest = interest begin, length = self.downloader.storage.new_request(interest) self.downloader.picker.requested(interest, self.have.numfalse == 0) self.active_requests.append((interest, begin, length)) self.connection.send_request(interest, begin, length) if not self.downloader.storage.do_I_have_requests(interest): lost_interests.append(interest) if not self.active_requests and self.interested: self.interested = False self.connection.send_not_interested() if lost_interests: for d in self.downloader.downloads: if d.active_requests or not d.interested: continue if d.example_interest is not None and self.downloader.storage.do_I_have_requests(d.example_interest): continue for lost in lost_interests: if d.have[lost]: break else: continue interest = self.downloader.picker.next(d._want, d.have.numfalse == 0) if interest is None: d.interested = False d.connection.send_not_interested() else: d.example_interest = interest if self.downloader.storage.is_endgame(): self.downloader.all_requests = [] for d in self.downloader.downloads: self.downloader.all_requests.extend(d.active_requests) for d in self.downloader.downloads: d.fix_download_endgame() def fix_download_endgame(self): want = [a for a in self.downloader.all_requests if self.have[a[0]] and a not in self.active_requests] if self.interested and not self.active_requests and not want: self.interested = False self.connection.send_not_interested() return if not self.interested and want: self.interested = True self.connection.send_interested() if self.choked: return shuffle(want) del want[self.downloader.backlog - len(self.active_requests):] self.active_requests.extend(want) for piece, begin, length in want: self.connection.send_request(piece, begin, length) def got_have(self, index): if self.have[index]: return self.have[index] = True self.downloader.picker.got_have(index) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: self.connection.close() return if self.downloader.storage.is_endgame(): self.fix_download_endgame() elif self.downloader.storage.do_I_have_requests(index): if not self.choked: self._request_more([index]) else: if not self.interested: self.interested = True self.connection.send_interested() def got_have_bitfield(self, have): self.have = have for i in xrange(len(self.have)): if self.have[i]: self.downloader.picker.got_have(i) if self.downloader.picker.am_I_complete() and self.have.numfalse == 0: self.connection.close() return if self.downloader.storage.is_endgame(): for piece, begin, length in self.downloader.all_requests: if self.have[piece]: self.interested = True self.connection.send_interested() return for i in xrange(len(self.have)): if self.have[i] and self.downloader.storage.do_I_have_requests(i): self.interested = True self.connection.send_interested() return def get_rate(self): return self.measure.get_rate() def is_snubbed(self): return time() - self.last > self.downloader.snub_time
class RateLimiter: def __init__(self, sched, unitsize, slotsfunc = lambda x: None): self.sched = sched self.last = None self.unitsize = unitsize self.slotsfunc = slotsfunc self.measure = Measure(MAX_RATE_PERIOD) self.autoadjust = False self.upload_rate = MAX_RATE * 1000 self.slots = SLOTS_STARTING # garbage if not automatic def set_upload_rate(self, rate): if DEBUG: print >>sys.stderr, time.asctime(),'-', "RateLimiter: set_upload_rate", rate # rate = -1 # test automatic if rate < 0: if self.autoadjust: return self.autoadjust = True self.autoadjustup = 0 self.pings = [] rate = MAX_RATE self.slots = SLOTS_STARTING self.slotsfunc(self.slots) else: self.autoadjust = False if not rate: rate = MAX_RATE self.upload_rate = rate * 1000 self.lasttime = clock() self.bytes_sent = 0 def queue(self, conn): if DEBUG: print >>sys.stderr, time.asctime(),'-', "RateLimiter: queue", conn assert conn.next_upload is None if self.last is None: self.last = conn conn.next_upload = conn self.try_send(True) else: conn.next_upload = self.last.next_upload self.last.next_upload = conn # 2fastbt_ if not conn.connection.is_coordinator_con(): self.last = conn # _2fastbt def try_send(self, check_time = False): if DEBUG: print >>sys.stderr, time.asctime(),'-', "RateLimiter: try_send" t = clock() self.bytes_sent -= (t - self.lasttime) * self.upload_rate #print 'try_send: bytes_sent: %s' % self.bytes_sent self.lasttime = t if check_time: self.bytes_sent = max(self.bytes_sent, 0) cur = self.last.next_upload while self.bytes_sent <= 0: bytes = cur.send_partial(self.unitsize) self.bytes_sent += bytes self.measure.update_rate(bytes) if bytes == 0 or cur.backlogged(): if self.last is cur: self.last = None cur.next_upload = None break else: self.last.next_upload = cur.next_upload cur.next_upload = None cur = self.last.next_upload else: # 2fastbt_ if not cur.connection.is_coordinator_con() or not cur.upload.buffer: # _2fastbt self.last = cur cur = cur.next_upload # 2fastbt_ else: pass # _2fastbt else: # 01/04/10 Boudewijn: because we use a -very- small value # to indicate a 0bps rate, we will schedule the call to be # made in a very long time. This results in no upload for # a very long time. # # the try_send method has protection again calling to # soon, so we can simply schedule the call to be made # sooner. delay = min(5.0, self.bytes_sent / self.upload_rate) self.sched(self.try_send, delay) def adjust_sent(self, bytes): # if DEBUG: print >>sys.stderr, time.asctime(),'-', "RateLimiter: adjust_sent", bytes self.bytes_sent = min(self.bytes_sent+bytes, self.upload_rate*3) self.measure.update_rate(bytes) def ping(self, delay): ##raise Exception('Is this called?') if DEBUG: print >>sys.stderr, time.asctime(),'-', delay if not self.autoadjust: return self.pings.append(delay > PING_BOUNDARY) if len(self.pings) < PING_SAMPLES+PING_DISCARDS: return if DEBUG: print >>sys.stderr, time.asctime(),'-', 'RateLimiter: cycle' pings = sum(self.pings[PING_DISCARDS:]) del self.pings[:] if pings >= PING_THRESHHOLD: # assume flooded if self.upload_rate == MAX_RATE: self.upload_rate = self.measure.get_rate()*ADJUST_DOWN else: self.upload_rate = min(self.upload_rate, self.measure.get_rate()*1.1) self.upload_rate = max(int(self.upload_rate*ADJUST_DOWN), 2) self.slots = int(sqrt(self.upload_rate*SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print >>sys.stderr, time.asctime(),'-', 'RateLimiter: adjust down to '+str(self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_FIRST else: # not flooded if self.upload_rate == MAX_RATE: return self.autoadjustup -= 1 if self.autoadjustup: return self.upload_rate = int(self.upload_rate*ADJUST_UP) self.slots = int(sqrt(self.upload_rate*SLOTS_FACTOR)) self.slotsfunc(self.slots) if DEBUG: print >>sys.stderr, time.asctime(),'-', 'RateLimiter: adjust up to '+str(self.upload_rate) self.lasttime = clock() self.bytes_sent = 0 self.autoadjustup = UP_DELAY_NEXT
class Upload: def __init__(self, connection, choker, storage, max_slice_length, max_rate_period, fudge): self.connection = connection self.choker = choker self.storage = storage self.max_slice_length = max_slice_length self.max_rate_period = max_rate_period # Whether this client is choking the peer. self.choked = True # Whether the peer is interested in data on this client. self.interested = False # (index, begin, length) tuples the peer has requested from this client. self.buffer = [] self.measure = Measure(max_rate_period, fudge) # Send our bitfield to the peer if we have any pieces. if storage.do_I_have_anything(): connection.send_bitfield(storage.get_have_list()) def got_not_interested(self): # The peer isn't interested in our data. if self.interested: self.interested = False # Don't proceed to send data that was previously requested. del self.buffer[:] self.choker.not_interested(self.connection) def got_interested(self): if not self.interested: self.interested = True self.choker.interested(self.connection) def flushed(self): # Send each requested (index, begin, length) part until there is backpressure. while len(self.buffer) > 0 and self.connection.is_flushed(): index, begin, length = self.buffer[0] del self.buffer[0] piece = self.storage.get_piece(index, begin, length) if piece is None: # The peer requested a bad piece, so we're done. self.connection.close() return self.measure.update_rate(len(piece)) self.connection.send_piece(index, begin, piece) def got_request(self, index, begin, length): if not self.interested or length > self.max_slice_length: # The peer requested data without sending an interested message first. # Or the peer is requesting too much data. self.connection.close() return if not self.choked: # We're not choking this peer, so enqueue and then try to fulfill the request. self.buffer.append((index, begin, length)) self.flushed() def got_cancel(self, index, begin, length): try: # The peer canceled a request. self.buffer.remove((index, begin, length)) except ValueError: pass def choke(self): if not self.choked: # This client is now choking the peer, so cancel all outstanding requests. self.choked = True del self.buffer[:] # Notify the peer that it has been choked by this client. self.connection.send_choke() def unchoke(self): if self.choked: # This client is no longer choking the peer. self.choked = False # Notify the peer that it is no longer choked by this client. self.connection.send_unchoke() def is_choked(self): return self.choked def is_interested(self): return self.interested def has_queries(self): return len(self.buffer) > 0 def get_rate(self): return self.measure.get_rate()