1 # Written by Bram Cohen, Pawel Garbacki, Boxun Zhang
2 # see LICENSE.txt for license information
4 from random import randrange, shuffle
7 from BaseLib.Core.BitTornado.clock import clock
18 def __init__(self, config, schedule, picker, seeding_selector, done = lambda: False):
20 self.round_robin_period = config['round_robin_period']
21 self.schedule = schedule
24 self.last_preferred = 0
25 self.last_round_robin = clock()
27 self.super_seed = False
29 schedule(self._round_robin, 5)
32 self.seeding_manager = None
35 def set_round_robin_period(self, x):
36 self.round_robin_period = x
38 def _round_robin(self):
39 self.schedule(self._round_robin, 5)
41 cons = range(len(self.connections))
43 count = self.config['min_uploads']-self.last_preferred
44 if count > 0: # optimization
48 if self.seeding_manager is None or self.seeding_manager.is_conn_eligible(c):
50 i = self.picker.next_have(self.connections[c], count > 0)
54 to_close.append(self.connections[c])
56 self.connections[c].send_have(i)
59 # Drop non-eligible connections
60 to_close.append(self.connections[c])
63 if self.last_round_robin + self.round_robin_period < clock():
64 self.last_round_robin = clock()
65 for i in xrange(1, len(self.connections)):
66 c = self.connections[i]
69 if self.seeding_manager is None or self.seeding_manager.is_conn_eligible(c):
71 if u.is_choked() and u.is_interested():
72 self.connections = self.connections[i:] + self.connections[:i]
78 helper = self.picker.helper
79 if helper is not None and helper.coordinator is None and helper.is_complete():
80 for c in self.connections:
81 if not c.connection.is_coordinator_con():
87 for c in self.connections:
88 c.get_upload().choke()
92 if 'unchoke_bias_for_internal' in self.config:
93 checkinternalbias = self.config['unchoke_bias_for_internal']
98 print >>sys.stderr,"choker: _rechoke: checkinternalbias",checkinternalbias
100 # 0. Construct candidate list
102 maxuploads = self.config['max_uploads']
105 # 1. Get some regular candidates
106 for c in self.connections:
108 # g2g: unchoke some g2g peers later
113 if self.seeding_manager is None or self.seeding_manager.is_conn_eligible(c):
115 if not u.is_interested():
122 if r < 1000 or d.is_snubbed():
126 if checkinternalbias and c.na_get_address_distance() == 0:
127 r += checkinternalbias
129 print >>sys.stderr,"choker: _rechoke: BIASING",c.get_ip(),c.get_port()
131 preferred.append((-r, c))
133 self.last_preferred = len(preferred)
135 del preferred[maxuploads-1:]
137 print >>sys.stderr,"choker: _rechoke: NORMAL UNCHOKE",preferred
138 preferred = [x[1] for x in preferred]
140 # 2. Get some g2g candidates
142 for c in self.connections:
147 if self.seeding_manager is None or self.seeding_manager.is_conn_eligible(c):
150 if not u.is_interested():
154 if checkinternalbias and c.na_get_address_distance() == 0:
155 r[0] += checkinternalbias
156 r[1] += checkinternalbias
158 print >>sys.stderr,"choker: _rechoke: G2G BIASING",c.get_ip(),c.get_port()
160 g2g_preferred.append((-r[0], -r[1], c))
163 del g2g_preferred[maxuploads-1:]
165 print >>sys.stderr,"choker: _rechoke: G2G UNCHOKE",g2g_preferred
166 g2g_preferred = [x[2] for x in g2g_preferred]
168 preferred += g2g_preferred
172 count = len(preferred)
176 # 3. The live source must always unchoke its auxiliary seeders
178 if 'live_aux_seeders' in self.config:
180 for hostport in self.config['live_aux_seeders']:
181 for c in self.connections:
182 if c.get_ip() == hostport[0]:
185 #print >>sys.stderr,"Choker: _rechoke: LIVE: Permanently unchoking aux seed",hostport
187 # 4. Select from candidate lists, aux seeders always selected
188 for c in self.connections:
193 if count < maxuploads or not hit:
194 if self.seeding_manager is None or self.seeding_manager.is_conn_eligible(c):
196 if u.is_interested():
198 if DEBUG and not hit: print >>sys.stderr,"choker: OPTIMISTIC UNCHOKE",c
202 if not c.connection.is_coordinator_con() and not c.connection.is_helper_con():
207 # 5. Unchoke selected candidates
212 def add_connection(self, connection, p = None):
214 Just add a connection, do not start doing anything yet
215 Must call "start_connection" later!
217 print >>sys.stderr, "Added connection",connection
219 p = randrange(-2, len(self.connections) + 1)
220 connection.get_upload().choke()
221 self.connections.insert(max(p, 0), connection)
222 self.picker.got_peer(connection)
225 def start_connection(self, connection):
226 connection.get_upload().unchoke()
228 def connection_made(self, connection, p = None):
230 p = randrange(-2, len(self.connections) + 1)
231 self.connections.insert(max(p, 0), connection)
232 self.picker.got_peer(connection)
235 def connection_lost(self, connection):
236 """ connection is a Connecter.Connection """
237 # Raynor Vliegendhart, RePEX:
238 # The RePEX code can close a connection right after the handshake
239 # but before the Choker has been informed via connection_made.
240 # However, Choker.connection_lost is still called when a connection
241 # is closed, so we should check whether Choker knows the connection:
242 if connection in self.connections:
243 self.connections.remove(connection)
244 self.picker.lost_peer(connection)
245 if connection.get_upload().is_interested() and not connection.get_upload().is_choked():
248 def interested(self, connection):
249 if not connection.get_upload().is_choked():
252 def not_interested(self, connection):
253 if not connection.get_upload().is_choked():
256 def set_super_seed(self):
257 while self.connections: # close all connections
258 self.connections[0].close()
259 self.picker.set_superseed()
260 self.super_seed = True
262 def pause(self, flag):
267 def set_seeding_manager(self, manager):
268 # When seeding starts, a non-trivial seeding manager will be set
269 self.seeding_manager = manager