1
0

PiecePicker.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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 PiecePicker:
  11. def __init__(self, numpieces,
  12. rarest_first_cutoff = 1, rarest_first_priority_cutoff = 3,
  13. priority_step = 20):
  14. self.rarest_first_cutoff = rarest_first_cutoff
  15. self.rarest_first_priority_cutoff = rarest_first_priority_cutoff + priority_step
  16. self.priority_step = priority_step
  17. self.cutoff = rarest_first_priority_cutoff
  18. self.numpieces = numpieces
  19. self.started = []
  20. self.totalcount = 0
  21. self.numhaves = [0] * numpieces
  22. self.priority = [1] * numpieces
  23. self.removed_partials = {}
  24. self.crosscount = [numpieces]
  25. self.crosscount2 = [numpieces]
  26. self.has = [0] * numpieces
  27. self.numgot = 0
  28. self.done = False
  29. self.seed_connections = {}
  30. self.past_ips = {}
  31. self.seed_time = None
  32. self.superseed = False
  33. self.seeds_connected = 0
  34. self._init_interests()
  35. def _init_interests(self):
  36. self.interests = [[] for x in xrange(self.priority_step)]
  37. self.level_in_interests = [self.priority_step] * self.numpieces
  38. interests = range(self.numpieces)
  39. shuffle(interests)
  40. self.pos_in_interests = [0] * self.numpieces
  41. for i in xrange(self.numpieces):
  42. self.pos_in_interests[interests[i]] = i
  43. self.interests.append(interests)
  44. def got_have(self, piece):
  45. self.totalcount+=1
  46. numint = self.numhaves[piece]
  47. self.numhaves[piece] += 1
  48. self.crosscount[numint] -= 1
  49. if numint+1==len(self.crosscount):
  50. self.crosscount.append(0)
  51. self.crosscount[numint+1] += 1
  52. if not self.done:
  53. numintplus = numint+self.has[piece]
  54. self.crosscount2[numintplus] -= 1
  55. if numintplus+1 == len(self.crosscount2):
  56. self.crosscount2.append(0)
  57. self.crosscount2[numintplus+1] += 1
  58. numint = self.level_in_interests[piece]
  59. self.level_in_interests[piece] += 1
  60. if self.superseed:
  61. self.seed_got_haves[piece] += 1
  62. numint = self.level_in_interests[piece]
  63. self.level_in_interests[piece] += 1
  64. elif self.has[piece] or self.priority[piece] == -1:
  65. return
  66. if numint == len(self.interests) - 1:
  67. self.interests.append([])
  68. self._shift_over(piece, self.interests[numint], self.interests[numint + 1])
  69. def lost_have(self, piece):
  70. self.totalcount-=1
  71. numint = self.numhaves[piece]
  72. self.numhaves[piece] -= 1
  73. self.crosscount[numint] -= 1
  74. self.crosscount[numint-1] += 1
  75. if not self.done:
  76. numintplus = numint+self.has[piece]
  77. self.crosscount2[numintplus] -= 1
  78. self.crosscount2[numintplus-1] += 1
  79. numint = self.level_in_interests[piece]
  80. self.level_in_interests[piece] -= 1
  81. if self.superseed:
  82. numint = self.level_in_interests[piece]
  83. self.level_in_interests[piece] -= 1
  84. elif self.has[piece] or self.priority[piece] == -1:
  85. return
  86. self._shift_over(piece, self.interests[numint], self.interests[numint - 1])
  87. def _shift_over(self, piece, l1, l2):
  88. assert self.superseed or (not self.has[piece] and self.priority[piece] >= 0)
  89. parray = self.pos_in_interests
  90. p = parray[piece]
  91. assert l1[p] == piece
  92. q = l1[-1]
  93. l1[p] = q
  94. parray[q] = p
  95. del l1[-1]
  96. newp = randrange(len(l2)+1)
  97. if newp == len(l2):
  98. parray[piece] = len(l2)
  99. l2.append(piece)
  100. else:
  101. old = l2[newp]
  102. parray[old] = len(l2)
  103. l2.append(old)
  104. l2[newp] = piece
  105. parray[piece] = newp
  106. def got_seed(self):
  107. self.seeds_connected += 1
  108. self.cutoff = max(self.rarest_first_priority_cutoff-self.seeds_connected,0)
  109. def became_seed(self):
  110. self.got_seed()
  111. self.totalcount -= self.numpieces
  112. self.numhaves = [i-1 for i in self.numhaves]
  113. if self.superseed or not self.done:
  114. self.level_in_interests = [i-1 for i in self.level_in_interests]
  115. if self.interests:
  116. del self.interests[0]
  117. del self.crosscount[0]
  118. if not self.done:
  119. del self.crosscount2[0]
  120. def lost_seed(self):
  121. self.seeds_connected -= 1
  122. self.cutoff = max(self.rarest_first_priority_cutoff-self.seeds_connected,0)
  123. def requested(self, piece):
  124. if piece not in self.started:
  125. self.started.append(piece)
  126. def _remove_from_interests(self, piece, keep_partial = False):
  127. l = self.interests[self.level_in_interests[piece]]
  128. p = self.pos_in_interests[piece]
  129. assert l[p] == piece
  130. q = l[-1]
  131. l[p] = q
  132. self.pos_in_interests[q] = p
  133. del l[-1]
  134. try:
  135. self.started.remove(piece)
  136. if keep_partial:
  137. self.removed_partials[piece] = 1
  138. except ValueError:
  139. pass
  140. def complete(self, piece):
  141. assert not self.has[piece]
  142. self.has[piece] = 1
  143. self.numgot += 1
  144. if self.numgot == self.numpieces:
  145. self.done = True
  146. self.crosscount2 = self.crosscount
  147. else:
  148. numhaves = self.numhaves[piece]
  149. self.crosscount2[numhaves] -= 1
  150. if numhaves+1 == len(self.crosscount2):
  151. self.crosscount2.append(0)
  152. self.crosscount2[numhaves+1] += 1
  153. self._remove_from_interests(piece)
  154. def next(self, haves, wantfunc, complete_first = False):
  155. cutoff = self.numgot < self.rarest_first_cutoff
  156. complete_first = (complete_first or cutoff) and not haves.complete()
  157. best = None
  158. bestnum = 2 ** 30
  159. for i in self.started:
  160. if haves[i] and wantfunc(i):
  161. if self.level_in_interests[i] < bestnum:
  162. best = i
  163. bestnum = self.level_in_interests[i]
  164. if best is not None:
  165. if complete_first or (cutoff and len(self.interests) > self.cutoff):
  166. return best
  167. if haves.complete():
  168. r = [ (0, min(bestnum,len(self.interests))) ]
  169. elif cutoff and len(self.interests) > self.cutoff:
  170. r = [ (self.cutoff, min(bestnum,len(self.interests))),
  171. (0, self.cutoff) ]
  172. else:
  173. r = [ (0, min(bestnum,len(self.interests))) ]
  174. for lo,hi in r:
  175. for i in xrange(lo,hi):
  176. for j in self.interests[i]:
  177. if haves[j] and wantfunc(j):
  178. return j
  179. if best is not None:
  180. return best
  181. return None
  182. def am_I_complete(self):
  183. return self.done
  184. def bump(self, piece):
  185. l = self.interests[self.level_in_interests[piece]]
  186. pos = self.pos_in_interests[piece]
  187. del l[pos]
  188. l.append(piece)
  189. for i in range(pos,len(l)):
  190. self.pos_in_interests[l[i]] = i
  191. try:
  192. self.started.remove(piece)
  193. except:
  194. pass
  195. def set_priority(self, piece, p):
  196. if self.superseed:
  197. return False # don't muck with this if you're a superseed
  198. oldp = self.priority[piece]
  199. if oldp == p:
  200. return False
  201. self.priority[piece] = p
  202. if p == -1:
  203. # when setting priority -1,
  204. # make sure to cancel any downloads for this piece
  205. if not self.has[piece]:
  206. self._remove_from_interests(piece, True)
  207. return True
  208. if oldp == -1:
  209. level = self.numhaves[piece] + (self.priority_step * p)
  210. self.level_in_interests[piece] = level
  211. if self.has[piece]:
  212. return True
  213. while len(self.interests) < level+1:
  214. self.interests.append([])
  215. l2 = self.interests[level]
  216. parray = self.pos_in_interests
  217. newp = randrange(len(l2)+1)
  218. if newp == len(l2):
  219. parray[piece] = len(l2)
  220. l2.append(piece)
  221. else:
  222. old = l2[newp]
  223. parray[old] = len(l2)
  224. l2.append(old)
  225. l2[newp] = piece
  226. parray[piece] = newp
  227. if self.removed_partials.has_key(piece):
  228. del self.removed_partials[piece]
  229. self.started.append(piece)
  230. # now go to downloader and try requesting more
  231. return True
  232. numint = self.level_in_interests[piece]
  233. newint = numint + ((p - oldp) * self.priority_step)
  234. self.level_in_interests[piece] = newint
  235. if self.has[piece]:
  236. return False
  237. while len(self.interests) < newint+1:
  238. self.interests.append([])
  239. self._shift_over(piece, self.interests[numint], self.interests[newint])
  240. return False
  241. def is_blocked(self, piece):
  242. return self.priority[piece] < 0
  243. def set_superseed(self):
  244. assert self.done
  245. self.superseed = True
  246. self.seed_got_haves = [0] * self.numpieces
  247. self._init_interests() # assume everyone is disconnected
  248. def next_have(self, connection, looser_upload):
  249. if self.seed_time is None:
  250. self.seed_time = clock()
  251. return None
  252. if clock() < self.seed_time+10: # wait 10 seconds after seeing the first peers
  253. return None # to give time to grab have lists
  254. if not connection.upload.super_seeding:
  255. return None
  256. olddl = self.seed_connections.get(connection)
  257. if olddl is None:
  258. ip = connection.get_ip()
  259. olddl = self.past_ips.get(ip)
  260. if olddl is not None: # peer reconnected
  261. self.seed_connections[connection] = olddl
  262. if not looser_upload:
  263. self.seed_got_haves[olddl] -= 1 # penalize
  264. if olddl is not None:
  265. if looser_upload:
  266. num = 1 # send a new have even if it hasn't spread that piece elsewhere
  267. else:
  268. num = 2
  269. if self.seed_got_haves[olddl] < num:
  270. return None
  271. if not connection.upload.was_ever_interested: # it never downloaded it?
  272. connection.upload.skipped_count += 1
  273. if connection.upload.skipped_count >= 3: # probably another stealthed seed
  274. return -1 # signal to close it
  275. for tier in self.interests:
  276. for piece in tier:
  277. if not connection.download.have[piece]:
  278. seedint = self.level_in_interests[piece]
  279. self.level_in_interests[piece] += 1 # tweak it up one, so you don't duplicate effort
  280. if seedint == len(self.interests) - 1:
  281. self.interests.append([])
  282. self._shift_over(piece,
  283. self.interests[seedint], self.interests[seedint + 1])
  284. self.seed_got_haves[piece] = 0 # reset this
  285. self.seed_connections[connection] = piece
  286. connection.upload.seed_have_list.append(piece)
  287. return piece
  288. return -1 # something screwy; terminate connection
  289. def lost_peer(self, connection):
  290. olddl = self.seed_connections.get(connection)
  291. if olddl is None:
  292. return
  293. del self.seed_connections[connection]
  294. self.past_ips[connection.get_ip()] = olddl
  295. if self.seed_got_haves[olddl] == 1:
  296. self.seed_got_haves[olddl] = 0