def __init__(self, config, eventScheduler, connHandler): self.config = config self.sched = eventScheduler self.connHandler = connHandler self.torrents = {} self.randomSlotLimiter = ManualQuotaLimiter(1) self.normalSlotLimiter = ManualQuotaLimiter(1) self._setSlotLimits(self.config.get('choker','maxSlots'), self.config.get('choker','randomSlotRatio')) self.chokeEventId = None self.chokeIntervalConfigId = None self.slotLimitConfigId = None self.log = logging.getLogger('Choker') self.lock = threading.RLock() self._start()
class Choker: def __init__(self, config, eventScheduler, connHandler): self.config = config self.sched = eventScheduler self.connHandler = connHandler self.torrents = {} self.randomSlotLimiter = ManualQuotaLimiter(1) self.normalSlotLimiter = ManualQuotaLimiter(1) self._setSlotLimits(self.config.get('choker','maxSlots'), self.config.get('choker','randomSlotRatio')) self.chokeEventId = None self.chokeIntervalConfigId = None self.slotLimitConfigId = None self.log = logging.getLogger('Choker') self.lock = threading.RLock() self._start() ##internal functions - torrent def _addTorrent(self, torrentIdent, ownStatus, superSeedingHandler): assert (not torrentIdent in self.torrents), 'Torrent already added?!' self.torrents[torrentIdent] = {'ownStatus':ownStatus, 'superSeedingHandler':superSeedingHandler} self.randomSlotLimiter.addUser(torrentIdent) self.normalSlotLimiter.addUser(torrentIdent) def _removeTorrent(self, torrentIdent): self.log.debug('Removing from dict') del self.torrents[torrentIdent] self.log.debug('Removing from random limiter') self.randomSlotLimiter.removeUser(torrentIdent) self.log.debug('Removing from normal limiter') self.normalSlotLimiter.removeUser(torrentIdent) self.log.debug('Finished') ##internal functions - choking def _setSlotLimits(self, maxSlots, randomRatio): randomSlots = max(1, int(maxSlots * randomRatio)) normalSlots = maxSlots - randomSlots self.randomSlotLimiter.changeLimit(randomSlots) self.normalSlotLimiter.changeLimit(normalSlots) def _chokeTorrent(self, torrentIdent, conns, uploadableConns, randomSlots, normalSlots, isFinished): shouldUpload = set() uploadingConns = set(conn for conn in conns if not conn.localChoked()) if len(uploadableConns) == 0: #no conn is uploadable, nothing to do self.log.debug('%s - Nothing to do', torrentIdent) else: if randomSlots > 0: #use random slots randConns = sample(list(uploadableConns), randomSlots) for conn in randConns: self.log.debug('%s - conn "%d": Picked this conn as a random upload target', torrentIdent, conn.fileno()) shouldUpload.add(conn) uploadableConns.remove(conn) #create list for comparing the others if isFinished: compareList = [(conn.localInterested(), max(conn.getScore(), 1.0), random(), conn) for conn in uploadableConns] else: compareList = [(conn.localInterested(), conn.getScore(), 0.0, conn) for conn in uploadableConns] compareList.sort(reverse=True) for connSet in compareList: self.log.debug('%s - conn "%d": Possible Upload candidate with local Interest "%d", score "%f" and random "%f" (payload ratio "%f")', torrentIdent, connSet[3].fileno(), connSet[0], connSet[1], connSet[2], connSet[3].getPayloadRatio()) #get needed conns for connSet in compareList[:normalSlots]: self.log.debug('%s - conn "%d": Decided to upload to this peer', torrentIdent, connSet[3].fileno()) shouldUpload.add(connSet[3]) #change choke status accordingly unchokeConns = shouldUpload.difference(uploadingConns) for conn in unchokeConns: self.log.debug('%s - conn "%d": Unchoking', torrentIdent, conn.fileno()) conn.setLocalChoke(False) #choke conns chokeConns = uploadingConns.difference(shouldUpload) for conn in chokeConns: self.log.debug('%s - conn "%d": Choking', torrentIdent, conn.fileno()) conn.setLocalChoke(True) def _chokeForTorrentLimits(self): randomSlots = max(1, int(self.config.get('choker','maxSlots') * self.config.get('choker','randomSlotRatio'))) normalSlots = self.config.get('choker','maxSlots') - randomSlots assert randomSlots > 0, 'No random slots?!' assert normalSlots > 0, 'No normal slots?!' for torrentIdent in self.torrents.iterkeys(): superSeedingHandler = self.torrents[torrentIdent]['superSeedingHandler'] ownStatus = self.torrents[torrentIdent]['ownStatus'] gotPieces = ownStatus.getGotPieces() isFinished = ownStatus.isFinished() conns = self.connHandler.getAllConnections(torrentIdent) if superSeedingHandler.isEnabled(): uploadableConns = set(conn for conn in conns if conn.remoteInterested() and superSeedingHandler.hasOfferedPieces(conn.fileno())) else: uploadableConns = set(conn for conn in conns if conn.remoteInterested() and conn.getStatus().hasMatchingMissingPieces(gotPieces)) self._chokeTorrent(torrentIdent, conns, uploadableConns, randomSlots, normalSlots, isFinished) def _chokeForGlobalLimits(self): #get required torrent info torrentInfo = {} neededSlots = [] for torrentIdent in self.torrents.iterkeys(): superSeedingHandler = self.torrents[torrentIdent]['superSeedingHandler'] ownStatus = self.torrents[torrentIdent]['ownStatus'] gotPieces = ownStatus.getGotPieces() info = {} info['gotPieces'] = gotPieces info['isFinished'] = ownStatus.isFinished() info['conns'] = self.connHandler.getAllConnections(torrentIdent) if superSeedingHandler.isEnabled(): info['uploadableConns'] = set(conn for conn in info['conns'] if conn.remoteInterested() and superSeedingHandler.hasOfferedPieces(conn.fileno())) else: info['uploadableConns'] = set(conn for conn in info['conns'] if conn.remoteInterested() and conn.getStatus().hasMatchingMissingPieces(gotPieces)) info['neededSlots'] = len(info['uploadableConns']) torrentInfo[torrentIdent] = info if info['neededSlots'] > 0: neededSlots.append((info['neededSlots'], torrentIdent)) #allocate slots normalSlots = self.normalSlotLimiter.getQuotas(neededSlots) neededSlots = [(slots - normalSlots[torrentIdent], torrentIdent) for slots, torrentIdent in neededSlots] randomSlots = self.randomSlotLimiter.getQuotas(neededSlots) #choke totalRandomSlots = 0 totalNormalSlots = 0 for torrentIdent, info in torrentInfo.iteritems(): self.log.info('%s - random slots %i, normal slots %i, needed slots %s', torrentIdent, randomSlots[torrentIdent], normalSlots[torrentIdent], str(info['neededSlots'])) totalRandomSlots += randomSlots[torrentIdent] totalNormalSlots += normalSlots[torrentIdent] self._chokeTorrent(torrentIdent, info['conns'], info['uploadableConns'], randomSlots[torrentIdent], normalSlots[torrentIdent], info['isFinished']) self.log.info('Used %i/%i random slots and %i/%i normal slots', totalRandomSlots, self.randomSlotLimiter.getLimit(), totalNormalSlots, self.normalSlotLimiter.getLimit()) ##internal functions - other def _start(self): if self.chokeEventId is None: #add event chokeInterval = self.config.get('choker','chokeInterval') self.chokeEventId = self.sched.scheduleEvent(self.choke, timedelta=chokeInterval, repeatdelta=chokeInterval) #add callback self.slotLimitConfigId = self.config.addCallback((('choker','maxSlots'),('choker','randomSlotRatio')) , self.changeSlotLimits, callType='value-funcArgAll', callWithAllOptions=True) self.chokeIntervalConfigId = self.config.addCallback((('choker','chokeInterval'),), self.changeChokeInterval) def _stop(self): if self.chokeEventId is not None: #remove callbacks self.log.debug('Removing callbacks') self.config.removeCallback(self.slotLimitConfigId) self.config.removeCallback(self.chokeIntervalConfigId) #remove event self.log.debug('Removing Events') self.sched.removeEvent(self.chokeEventId) self.chokeEventId = None self.chokeIntervalConfigId = None self.slotLimitConfigId = None self.log.debug('Finished') ##external functions - choking def choke(self): self.lock.acquire() if self.chokeEventId is not None: self.log.info('Choking ...') if self.config.get('choker','slotLimitScope') == 'global': self._chokeForGlobalLimits() else: self._chokeForTorrentLimits() self.log.info('Choking finished') self.lock.release() ##external functions - torrents def addTorrent(self, torrentIdent, ownStatus, superSeedingHandler): self.lock.acquire() self._addTorrent(torrentIdent, ownStatus, superSeedingHandler) self.lock.release() def removeTorrent(self, torrentIdent): self.lock.acquire() self._removeTorrent(torrentIdent) self.lock.release() ##external functions - other def start(self): self.lock.acquire() self._start() self.lock.release() def stop(self): self.lock.acquire() self._stop() self.lock.release() def changeChokeInterval(self, newInterval): self.lock.acquire() if self.chokeEventId is not None: self.sched.changeEvent(self.chokeEventId, repeatdelta=newInterval) self.sched.rescheduleEvent(self.chokeEventId, timedelta=newInterval) self.lock.release() def changeSlotLimits(self, maxSlots, randomSlotRatio): assert type(randomSlotRatio) == float, 'Invalid type %s for randomSlotRatio!' % (str(type(randomSlotRatio)),) self.lock.acquire() self._setSlotLimits(maxSlots, randomSlotRatio) self.lock.release()