diff options
Diffstat (limited to 'NohGooee/Choker.py')
-rw-r--r-- | NohGooee/Choker.py | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/NohGooee/Choker.py b/NohGooee/Choker.py new file mode 100644 index 0000000..ed3d500 --- /dev/null +++ b/NohGooee/Choker.py @@ -0,0 +1,157 @@ +# The contents of this file are subject to the BitTorrent Open Source License +# Version 1.1 (the License). You may not copy or use this file, in either +# source code or executable form, except in compliance with the License. You +# may obtain a copy of the License at http://www.bittorrent.com/license/. +# +# Software distributed under the License is distributed on an AS IS basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. + +# Written by Bram Cohen + +from random import randrange +from math import sqrt + +class Choker(object): + + def __init__(self, config, schedule, done = lambda: False): + self.config = config + self.schedule = schedule + self.connections = [] + self.count = 0 + self.done = done + self.unchokes_since_last = 0 + schedule(self._round_robin, 10) + + def _round_robin(self): + self.schedule(self._round_robin, 10) + self.count += 1 + if self.done(): + self._rechoke_seed(True) + return + if self.count % 3 == 0: + for i in xrange(len(self.connections)): + u = self.connections[i].upload + if u.choked and u.interested: + self.connections = self.connections[i:] + self.connections[:i] + break + self._rechoke() + + def _rechoke(self): + if self.done(): + self._rechoke_seed() + return + preferred = [] + for i in xrange(len(self.connections)): + c = self.connections[i] + if c.upload.interested and not c.download.is_snubbed() and c.download.have.numfalse: + preferred.append((-c.download.get_rate(), i)) + preferred.sort() + prefcount = min(len(preferred), self._max_uploads() -1) + mask = [0] * len(self.connections) + for _, i in preferred[:prefcount]: + mask[i] = 1 + count = max(1, self.config['min_uploads'] - prefcount) + for i in xrange(len(self.connections)): + c = self.connections[i] + u = c.upload + if mask[i]: + u.unchoke(self.count) + elif count > 0 and c.download.have.numfalse: + u.unchoke(self.count) + if u.interested: + count -= 1 + else: + u.choke() + + def _rechoke_seed(self, force_new_unchokes = False): + if force_new_unchokes: + # number of unchokes per 30 second period + i = (self._max_uploads() + 2) // 3 + # this is called 3 times in 30 seconds, if i==4 then unchoke 1+1+2 + # and so on; substract unchokes recently triggered by disconnects + num_force_unchokes = max(0, (i + self.count % 3) // 3 - \ + self.unchokes_since_last) + else: + num_force_unchokes = 0 + preferred = [] + new_limit = self.count - 3 + for i in xrange(len(self.connections)): + c = self.connections[i] + u = c.upload + if not u.choked and u.interested and c.download.have.numfalse: + if u.unchoke_time > new_limit or ( + u.buffer and c.connection.is_flushed()): + preferred.append((-u.unchoke_time, -u.get_rate(), i)) + else: + preferred.append((1, -u.get_rate(), i)) + num_kept = self._max_uploads() - num_force_unchokes + assert num_kept >= 0 + preferred.sort() + preferred = preferred[:num_kept] + mask = [0] * len(self.connections) + for _, _, i in preferred: + mask[i] = 1 + num_nonpref = self._max_uploads() - len(preferred) + if force_new_unchokes: + self.unchokes_since_last = 0 + else: + self.unchokes_since_last += num_nonpref + last_unchoked = None + for i in xrange(len(self.connections)): + c = self.connections[i] + u = c.upload + if not mask[i]: + if not u.interested: + u.choke() + elif u.choked: + if num_nonpref > 0 and c.connection.is_flushed() and c.download.have.numfalse: + u.unchoke(self.count) + num_nonpref -= 1 + if num_nonpref == 0: + last_unchoked = i + else: + if num_nonpref == 0 or not c.download.have.numfalse: + u.choke() + else: + num_nonpref -= 1 + if num_nonpref == 0: + last_unchoked = i + if last_unchoked is not None: + self.connections = self.connections[last_unchoked + 1:] + \ + self.connections[:last_unchoked + 1] + + def connection_made(self, connection): + p = randrange(len(self.connections) + 1) + self.connections.insert(p, connection) + + def connection_lost(self, connection): + self.connections.remove(connection) + if connection.upload.interested and not connection.upload.choked: + self._rechoke() + + def interested(self, connection): + if not connection.upload.choked: + self._rechoke() + + def not_interested(self, connection): + if not connection.upload.choked: + self._rechoke() + + def _max_uploads(self): + uploads = self.config['max_uploads'] + rate = self.config['max_upload_rate'] + if uploads > 0: + pass + elif rate <= 0: + uploads = 7 # unlimited, just guess something here... + elif rate < 9: + uploads = 2 + elif rate < 15: + uploads = 3 + elif rate < 42: + uploads = 4 + else: + uploads = int(sqrt(rate * .6)) + return uploads |