Choker.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # Written by Bram Cohen
  2. # see LICENSE.txt for license information
  3. from random import randrange, shuffle
  4. from BitTornado.clock import clock
  5. try:
  6. True
  7. except:
  8. True = 1
  9. False = 0
  10. class Choker:
  11. def __init__(self, config, schedule, picker, done = lambda: False):
  12. self.config = config
  13. self.round_robin_period = config['round_robin_period']
  14. self.schedule = schedule
  15. self.picker = picker
  16. self.connections = []
  17. self.last_preferred = 0
  18. self.last_round_robin = clock()
  19. self.done = done
  20. self.super_seed = False
  21. self.paused = False
  22. schedule(self._round_robin, 5)
  23. def set_round_robin_period(self, x):
  24. self.round_robin_period = x
  25. def _round_robin(self):
  26. self.schedule(self._round_robin, 5)
  27. if self.super_seed:
  28. cons = range(len(self.connections))
  29. to_close = []
  30. count = self.config['min_uploads']-self.last_preferred
  31. if count > 0: # optimization
  32. shuffle(cons)
  33. for c in cons:
  34. i = self.picker.next_have(self.connections[c], count > 0)
  35. if i is None:
  36. continue
  37. if i < 0:
  38. to_close.append(self.connections[c])
  39. continue
  40. self.connections[c].send_have(i)
  41. count -= 1
  42. for c in to_close:
  43. c.close()
  44. if self.last_round_robin + self.round_robin_period < clock():
  45. self.last_round_robin = clock()
  46. for i in xrange(1, len(self.connections)):
  47. c = self.connections[i]
  48. u = c.get_upload()
  49. if u.is_choked() and u.is_interested():
  50. self.connections = self.connections[i:] + self.connections[:i]
  51. break
  52. self._rechoke()
  53. def _rechoke(self):
  54. preferred = []
  55. maxuploads = self.config['max_uploads']
  56. if self.paused:
  57. for c in self.connections:
  58. c.get_upload().choke()
  59. return
  60. if maxuploads > 1:
  61. for c in self.connections:
  62. u = c.get_upload()
  63. if not u.is_interested():
  64. continue
  65. if self.done():
  66. r = u.get_rate()
  67. else:
  68. d = c.get_download()
  69. r = d.get_rate()
  70. if r < 1000 or d.is_snubbed():
  71. continue
  72. preferred.append((-r, c))
  73. self.last_preferred = len(preferred)
  74. preferred.sort()
  75. del preferred[maxuploads-1:]
  76. preferred = [x[1] for x in preferred]
  77. count = len(preferred)
  78. hit = False
  79. to_unchoke = []
  80. for c in self.connections:
  81. u = c.get_upload()
  82. if c in preferred:
  83. to_unchoke.append(u)
  84. else:
  85. if count < maxuploads or not hit:
  86. to_unchoke.append(u)
  87. if u.is_interested():
  88. count += 1
  89. hit = True
  90. else:
  91. u.choke()
  92. for u in to_unchoke:
  93. u.unchoke()
  94. def connection_made(self, connection, p = None):
  95. if p is None:
  96. p = randrange(-2, len(self.connections) + 1)
  97. self.connections.insert(max(p, 0), connection)
  98. self._rechoke()
  99. def connection_lost(self, connection):
  100. self.connections.remove(connection)
  101. self.picker.lost_peer(connection)
  102. if connection.get_upload().is_interested() and not connection.get_upload().is_choked():
  103. self._rechoke()
  104. def interested(self, connection):
  105. if not connection.get_upload().is_choked():
  106. self._rechoke()
  107. def not_interested(self, connection):
  108. if not connection.get_upload().is_choked():
  109. self._rechoke()
  110. def set_super_seed(self):
  111. while self.connections: # close all connections
  112. self.connections[0].close()
  113. self.picker.set_superseed()
  114. self.super_seed = True
  115. def pause(self, flag):
  116. self.paused = flag
  117. self._rechoke()