2015-08-20 17:27:15 +02:00
|
|
|
import binascii
|
|
|
|
import collections
|
2016-11-03 20:42:45 +01:00
|
|
|
import logging
|
2016-12-14 23:52:26 +01:00
|
|
|
import time
|
2016-11-03 20:42:45 +01:00
|
|
|
|
|
|
|
from twisted.internet import defer, reactor
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
2015-08-20 17:27:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
class DHTHashAnnouncer(object):
|
2017-02-08 00:17:03 +01:00
|
|
|
callLater = reactor.callLater
|
|
|
|
ANNOUNCE_CHECK_INTERVAL = 60
|
|
|
|
CONCURRENT_ANNOUNCERS = 5
|
|
|
|
|
2015-08-20 17:27:15 +02:00
|
|
|
"""This class announces to the DHT that this peer has certain blobs"""
|
|
|
|
def __init__(self, dht_node, peer_port):
|
|
|
|
self.dht_node = dht_node
|
|
|
|
self.peer_port = peer_port
|
|
|
|
self.suppliers = []
|
|
|
|
self.next_manage_call = None
|
|
|
|
self.hash_queue = collections.deque()
|
|
|
|
self._concurrent_announcers = 0
|
|
|
|
|
|
|
|
def run_manage_loop(self):
|
|
|
|
if self.peer_port is not None:
|
|
|
|
self._announce_available_hashes()
|
2017-02-08 00:17:03 +01:00
|
|
|
self.next_manage_call = self.callLater(self.ANNOUNCE_CHECK_INTERVAL, self.run_manage_loop)
|
2015-08-20 17:27:15 +02:00
|
|
|
|
|
|
|
def stop(self):
|
2016-11-03 20:42:45 +01:00
|
|
|
log.info("Stopping %s", self)
|
2015-08-20 17:27:15 +02:00
|
|
|
if self.next_manage_call is not None:
|
|
|
|
self.next_manage_call.cancel()
|
|
|
|
self.next_manage_call = None
|
|
|
|
|
|
|
|
def add_supplier(self, supplier):
|
|
|
|
self.suppliers.append(supplier)
|
|
|
|
|
|
|
|
def immediate_announce(self, blob_hashes):
|
|
|
|
if self.peer_port is not None:
|
|
|
|
return self._announce_hashes(blob_hashes)
|
|
|
|
else:
|
|
|
|
return defer.succeed(False)
|
|
|
|
|
2017-02-08 00:17:03 +01:00
|
|
|
def hash_queue_size(self):
|
|
|
|
return len(self.hash_queue)
|
|
|
|
|
2015-08-20 17:27:15 +02:00
|
|
|
def _announce_available_hashes(self):
|
2016-12-11 00:02:13 +01:00
|
|
|
log.debug('Announcing available hashes')
|
2015-08-20 17:27:15 +02:00
|
|
|
ds = []
|
|
|
|
for supplier in self.suppliers:
|
|
|
|
d = supplier.hashes_to_announce()
|
|
|
|
d.addCallback(self._announce_hashes)
|
|
|
|
ds.append(d)
|
|
|
|
dl = defer.DeferredList(ds)
|
|
|
|
return dl
|
|
|
|
|
|
|
|
def _announce_hashes(self, hashes):
|
2016-12-14 23:52:26 +01:00
|
|
|
if not hashes:
|
|
|
|
return
|
|
|
|
log.debug('Announcing %s hashes', len(hashes))
|
|
|
|
# TODO: add a timeit decorator
|
|
|
|
start = time.time()
|
2015-08-20 17:27:15 +02:00
|
|
|
ds = []
|
|
|
|
|
|
|
|
for h in hashes:
|
|
|
|
announce_deferred = defer.Deferred()
|
|
|
|
ds.append(announce_deferred)
|
|
|
|
self.hash_queue.append((h, announce_deferred))
|
2017-02-08 00:17:03 +01:00
|
|
|
log.debug('There are now %s hashes remaining to be announced', self.hash_queue_size())
|
2015-08-20 17:27:15 +02:00
|
|
|
|
|
|
|
def announce():
|
|
|
|
if len(self.hash_queue):
|
|
|
|
h, announce_deferred = self.hash_queue.popleft()
|
2016-12-11 00:02:13 +01:00
|
|
|
log.debug('Announcing blob %s to dht', h)
|
2015-08-20 17:27:15 +02:00
|
|
|
d = self.dht_node.announceHaveBlob(binascii.unhexlify(h), self.peer_port)
|
|
|
|
d.chainDeferred(announce_deferred)
|
2017-02-08 00:17:03 +01:00
|
|
|
d.addBoth(lambda _: self.callLater(0, announce))
|
2015-08-20 17:27:15 +02:00
|
|
|
else:
|
|
|
|
self._concurrent_announcers -= 1
|
|
|
|
|
2017-02-08 00:17:03 +01:00
|
|
|
for i in range(self._concurrent_announcers, self.CONCURRENT_ANNOUNCERS):
|
2015-08-20 17:27:15 +02:00
|
|
|
self._concurrent_announcers += 1
|
|
|
|
announce()
|
2016-12-14 23:52:26 +01:00
|
|
|
d = defer.DeferredList(ds)
|
|
|
|
d.addCallback(lambda _: log.debug('Took %s seconds to announce %s hashes',
|
|
|
|
time.time() - start, len(hashes)))
|
|
|
|
return d
|
2015-08-20 17:27:15 +02:00
|
|
|
|
|
|
|
|
|
|
|
class DHTHashSupplier(object):
|
2017-02-08 00:17:03 +01:00
|
|
|
# 1 hour is the min time hash will be reannounced
|
|
|
|
MIN_HASH_REANNOUNCE_TIME = 60*60
|
|
|
|
# conservative assumption of the time it takes to announce
|
|
|
|
# a single hash
|
|
|
|
SINGLE_HASH_ANNOUNCE_DURATION = 1
|
|
|
|
|
2015-08-20 17:27:15 +02:00
|
|
|
"""Classes derived from this class give hashes to a hash announcer"""
|
|
|
|
def __init__(self, announcer):
|
|
|
|
if announcer is not None:
|
|
|
|
announcer.add_supplier(self)
|
|
|
|
self.hash_announcer = announcer
|
|
|
|
|
|
|
|
def hashes_to_announce(self):
|
2016-08-08 22:42:35 +02:00
|
|
|
pass
|
2017-02-08 00:17:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
def get_next_announce_time(self, num_hashes_to_announce=1):
|
|
|
|
"""
|
|
|
|
Hash reannounce time is set to current time + MIN_HASH_REANNOUNCE_TIME,
|
|
|
|
unless we are announcing a lot of hashes at once which could cause the
|
|
|
|
the announce queue to pile up. To prevent pile up, reannounce
|
|
|
|
only after a conservative estimate of when it will finish
|
|
|
|
to announce all the hashes.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
num_hashes_to_announce: number of hashes that will be added to the queue
|
|
|
|
Returns:
|
|
|
|
timestamp for next announce time
|
|
|
|
"""
|
|
|
|
queue_size = self.hash_announcer.hash_queue_size()+num_hashes_to_announce
|
|
|
|
reannounce = max(self.MIN_HASH_REANNOUNCE_TIME,
|
|
|
|
queue_size*self.SINGLE_HASH_ANNOUNCE_DURATION)
|
|
|
|
return time.time() + reannounce
|
|
|
|
|
|
|
|
|