home *** CD-ROM | disk | FTP | other *** search
/ Chip 2006 June / CHIP 2006-06.2.iso / program / freeware / Democracy-0.8.2.exe / xulrunner / python / BitTorrent / Choker.py < prev    next >
Encoding:
Python Source  |  2006-04-10  |  5.2 KB  |  140 lines

  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.0 (the License).  You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License.  You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10.  
  11. # Written by Bram Cohen
  12.  
  13. from random import randrange
  14.  
  15. class Choker(object):
  16.  
  17.     def __init__(self, config, schedule, done = lambda: False):
  18.         self.config = config
  19.         self.schedule = schedule
  20.         self.connections = []
  21.         self.count = 0
  22.         self.done = done
  23.         self.unchokes_since_last = 0
  24.         schedule(self._round_robin, 10)
  25.  
  26.     def _round_robin(self):
  27.         self.schedule(self._round_robin, 10)
  28.         self.count += 1
  29.         if self.done():
  30.             self._rechoke_seed(True)
  31.             return
  32.         if self.count % 3 == 0:
  33.             for i in xrange(len(self.connections)):
  34.                 u = self.connections[i].upload
  35.                 if u.choked and u.interested:
  36.                     self.connections = self.connections[i:] + self.connections[:i]
  37.                     break
  38.         self._rechoke()
  39.  
  40.     def _rechoke(self):
  41.         if self.done():
  42.             self._rechoke_seed()
  43.             return
  44.         preferred = []
  45.         for i in xrange(len(self.connections)):
  46.             c = self.connections[i]
  47.             if c.upload.interested and not c.download.is_snubbed():
  48.                 preferred.append((-c.download.get_rate(), i))
  49.         preferred.sort()
  50.         prefcount = min(len(preferred), self.config['max_uploads_internal'] -1)
  51.         mask = [0] * len(self.connections)
  52.         for _, i in preferred[:prefcount]:
  53.             mask[i] = 1
  54.         count = max(1, self.config['min_uploads'] - prefcount)
  55.         for i in xrange(len(self.connections)):
  56.             c = self.connections[i]
  57.             u = c.upload
  58.             if mask[i]:
  59.                 u.unchoke(self.count)
  60.             elif count > 0:
  61.                 u.unchoke(self.count)
  62.                 if u.interested:
  63.                     count -= 1
  64.             else:
  65.                 u.choke()
  66.  
  67.     def _rechoke_seed(self, force_new_unchokes = False):
  68.         if force_new_unchokes:
  69.             # number of unchokes per 30 second period
  70.             i = (self.config['max_uploads_internal'] + 2) // 3
  71.             # this is called 3 times in 30 seconds, if i==4 then unchoke 1+1+2
  72.             # and so on; substract unchokes recently triggered by disconnects
  73.             num_force_unchokes = max(0, (i + self.count % 3) // 3 - \
  74.                                  self.unchokes_since_last)
  75.         else:
  76.             num_force_unchokes = 0
  77.         preferred = []
  78.         new_limit = self.count - 3
  79.         for i in xrange(len(self.connections)):
  80.             c = self.connections[i]
  81.             u = c.upload
  82.             if not u.choked and u.interested:
  83.                 if u.unchoke_time > new_limit or (
  84.                         u.buffer and c.connection.is_flushed()):
  85.                     preferred.append((-u.unchoke_time, -u.get_rate(), i))
  86.                 else:
  87.                     preferred.append((1, -u.get_rate(), i))
  88.         num_kept = self.config['max_uploads_internal'] - num_force_unchokes
  89.         assert num_kept >= 0
  90.         preferred.sort()
  91.         preferred = preferred[:num_kept]
  92.         mask = [0] * len(self.connections)
  93.         for _, _, i in preferred:
  94.             mask[i] = 1
  95.         num_nonpref = self.config['max_uploads_internal'] - len(preferred)
  96.         if force_new_unchokes:
  97.             self.unchokes_since_last = 0
  98.         else:
  99.             self.unchokes_since_last += num_nonpref
  100.         last_unchoked = None
  101.         for i in xrange(len(self.connections)):
  102.             c = self.connections[i]
  103.             u = c.upload
  104.             if not mask[i]:
  105.                 if not u.interested:
  106.                     u.choke()
  107.                 elif u.choked:
  108.                     if num_nonpref > 0 and c.connection.is_flushed():
  109.                         u.unchoke(self.count)
  110.                         num_nonpref -= 1
  111.                         if num_nonpref == 0:
  112.                             last_unchoked = i
  113.                 else:
  114.                     if num_nonpref == 0:
  115.                         u.choke()
  116.                     else:
  117.                         num_nonpref -= 1
  118.                         if num_nonpref == 0:
  119.                             last_unchoked = i
  120.         if last_unchoked is not None:
  121.             self.connections = self.connections[last_unchoked + 1:] + \
  122.                                self.connections[:last_unchoked + 1]
  123.  
  124.     def connection_made(self, connection):
  125.         p = randrange(len(self.connections) + 1)
  126.         self.connections.insert(p, connection)
  127.  
  128.     def connection_lost(self, connection):
  129.         self.connections.remove(connection)
  130.         if connection.upload.interested and not connection.upload.choked:
  131.             self._rechoke()
  132.  
  133.     def interested(self, connection):
  134.         if not connection.upload.choked:
  135.             self._rechoke()
  136.  
  137.     def not_interested(self, connection):
  138.         if not connection.upload.choked:
  139.             self._rechoke()
  140.