Exemple #1
0
    def got_response_collecting(self, stream, request, response):
        logging.info("BitTorrent: collecting ... done")

        if self.success:
            #
            # Always measure at the receiver because there is more
            # information at the receiver and also to make my friend
            # Enrico happier :-P.
            # The following is not a bug: it's just that the server
            # returns a result using the point of view of the client,
            # i.e. upload_speed is _our_ upload speed.
            #
            m = json.loads(response.body.read())
            self.my_side["upload_speed"] = m["upload_speed"]

            upload = utils.speed_formatter(m["upload_speed"])
            STATE.update("test_progress", "100%", publish=False)
            STATE.update("test_upload", upload)
            logging.info('BitTorrent: upload speed: %s', upload)

            if privacy.collect_allowed(self.my_side):
                if DATABASE.readonly:
                    logging.warning('bittorrent_client: readonly database')
                else:
                    table_bittorrent.insert(DATABASE.connection(), self.my_side)

            # Update the upstream channel estimate
            target_bytes = int(m["target_bytes"])
            if target_bytes > 0:
                estimate.UPLOAD = target_bytes

            self.final_state = True

        stream.close()
Exemple #2
0
    def got_response_collecting(self, stream, request, response):
        LOG.complete()

        if self.success:
            #
            # Always measure at the receiver because there is more
            # information at the receiver and also to make my friend
            # Enrico happier :-P.
            # The following is not a bug: it's just that the server
            # returns a result using the point of view of the client,
            # i.e. upload_speed is _our_ upload speed.
            #
            m = json.loads(response.body.read())
            self.my_side["upload_speed"] = m["upload_speed"]

            upload = utils.speed_formatter(m["upload_speed"])
            STATE.update("test_upload", upload)

            if privacy.collect_allowed(self.my_side):
                table_bittorrent.insert(DATABASE.connection(), self.my_side)

            # Update the upstream channel estimate
            target_bytes = int(m["target_bytes"])
            if target_bytes > 0:
                estimate.UPLOAD = target_bytes

        stream.close()
Exemple #3
0
 def _periodic_internal(stream):
     """ Periodically snap goodput (internal function) """
     context = stream.opaque
     utime, stime = os.times()[:2]
     utimediff = utime - context.snap_utime
     stimediff = stime - context.snap_stime
     ticks = utils.ticks()
     timediff = ticks - context.snap_ticks
     bytesdiff = stream.bytes_in - context.snap_count
     context.state.setdefault("goodput_snap", []).append(
         {
             "ticks": ticks,
             "bytesdiff": bytesdiff,
             "timediff": timediff,
             "utimediff": utimediff,
             "stimediff": stimediff,
         }
     )
     logging.debug("raw_clnt: utime, stime = %f, %f", utime, stime)
     if timediff > 1e-06:
         speed = utils.speed_formatter(bytesdiff / timediff)
         logging.debug("raw_clnt: goodput_snap: %s", speed)
     context.snap_count = stream.bytes_in
     context.snap_ticks = ticks
     context.snap_utime = utime
     context.snap_stime = stime
Exemple #4
0
def main():
    assert(len(RANDOMBLOCKS.get_block()) == RANDOMBLOCKS.blocksiz)
    assert(RANDOMBLOCKS.get_block() != RANDOMBLOCKS.get_block())

    fp, total = RandomBody(RANDOMBLOCKS.blocksiz + 789), 0
    while True:
        block = fp.read(128)
        if not block:
            break
        total += len(block)
    assert(total == RANDOMBLOCKS.blocksiz + 789)

    fp = RandomBody(RANDOMBLOCKS.blocksiz + 789)
    assert(len(fp.read()) == RANDOMBLOCKS.blocksiz)
    assert(fp.tell() == 789)
    assert(len(fp.read()) == 789)
    fp.seek(7)

    begin, total = utils.ticks(), 0
    try:
        while True:
            total += len(RANDOMBLOCKS.get_block())
    except KeyboardInterrupt:
        print "Interrupt"

    print "Speed:", utils.speed_formatter(total/(utils.ticks() - begin))
def main():

    ''' Unit test for neubot/utils_random.py '''

    assert(len(RANDOMBLOCKS.get_block()) == RANDOMBLOCKS.blocksiz)
    assert(RANDOMBLOCKS.get_block() != RANDOMBLOCKS.get_block())

    filep, total = RandomBody(RANDOMBLOCKS.blocksiz + 789), 0
    while True:
        block = filep.read(128)
        if not block:
            break
        total += len(block)
    assert(total == RANDOMBLOCKS.blocksiz + 789)

    filep = RandomBody(RANDOMBLOCKS.blocksiz + 789)
    assert(len(filep.read()) == RANDOMBLOCKS.blocksiz)
    assert(filep.tell() == 789)
    assert(len(filep.read()) == 789)
    filep.seek(7)

    begin, total = utils.ticks(), 0
    while total < 1073741824:
        total += len(RANDOMBLOCKS.get_block())
    elapsed = utils.ticks() - begin

    print('Elapsed: %s' % utils.time_formatter(elapsed))
    print('Speed: %s' % utils.speed_formatter(total/elapsed))
Exemple #6
0
def main():
    ''' Unit test for neubot/utils_random.py '''

    assert (len(RANDOMBLOCKS.get_block()) == RANDOMBLOCKS.blocksiz)
    assert (RANDOMBLOCKS.get_block() != RANDOMBLOCKS.get_block())

    filep, total = RandomBody(RANDOMBLOCKS.blocksiz + 789), 0
    while True:
        block = filep.read(128)
        if not block:
            break
        total += len(block)
    assert (total == RANDOMBLOCKS.blocksiz + 789)

    filep = RandomBody(RANDOMBLOCKS.blocksiz + 789)
    assert (len(filep.read()) == RANDOMBLOCKS.blocksiz)
    assert (filep.tell() == 789)
    assert (len(filep.read()) == 789)
    filep.seek(7)

    begin, total = utils.ticks(), 0
    while total < 1073741824:
        total += len(RANDOMBLOCKS.get_block())
    elapsed = utils.ticks() - begin

    print('Elapsed: %s' % utils.time_formatter(elapsed))
    print('Speed: %s' % utils.speed_formatter(total / elapsed))
Exemple #7
0
    def report(self, *args, **kwargs):
        self.poller.sched(self.interval, self.report)

        rttavg, rttdetails = self.measure_rtt()
        if len(rttdetails) > 0:
            rttavg = "%d us" % int(1000000 * rttavg)
            self.output.write("\t\t%s\t\t---\t\t---\n" % rttavg)
            if len(rttdetails) > 1:
                for detail in rttdetails:
                    detail = "%d us" % int(1000000 * detail)
                    self.output.write("\t\t  %s\t\t---\t\t---\n" % detail)

        recvavg, sendavg, percentages = self.measure_speed()
        if len(percentages) > 0:
            recv, send = (utils.speed_formatter(recvavg),
                          utils.speed_formatter(sendavg))
            self.output.write("\t\t---\t\t%s\t\t%s\n" % (recv, send))
            if len(percentages) > 1:
                for val in percentages:
                    val = map(lambda x: "%.2f%%" % x, val)
                    self.output.write("\t\t---\t\t  %s\t\t  %s\n" %
                                      (val[0], val[1]))
Exemple #8
0
 def _waiting_piece(self, stream, data):
     ''' Invoked when new data is available '''
     # Note: this loop cannot be adapted to process other messages
     # easily, as pointed out in <skype_defs.py>.
     context = stream.opaque
     context.bufferise(data)
     context.state['rcvr_data'].append((utils.ticks(), len(data)))
     while True:
         if context.left > 0:
             context.left = context.skip(context.left)
             if context.left > 0:
                 break
         elif context.left == 0:
             tmp = context.pullup(4)
             if not tmp:
                 break
             context.left, = struct.unpack('!I', tmp)
             if context.left > MAXRECV:
                 raise RuntimeError('skype_clnt: PIECE too large')
             if not context.ticks:
                 context.ticks = context.snap_ticks = utils.ticks()
                 context.count = context.snap_count = stream.bytes_in
                 context.snap_utime, context.snap_stime = os.times()[:2]
                 POLLER.sched(1, self._periodic, stream)
             if context.left == 0:
                 logging.debug('< {empty-message}')
                 logging.info('skype_clnt: skype goodput test... complete')
                 ticks = utils.ticks()
                 timediff = ticks - context.ticks
                 bytesdiff = stream.bytes_in - context.count
                 context.state['goodput'] = {
                                             'ticks': ticks,
                                             'bytesdiff': bytesdiff,
                                             'timediff': timediff,
                                            }
                 if timediff > 1e-06:
                     speed = utils.speed_formatter(bytesdiff / timediff)
                     logging.info('skype_clnt: goodput: %s', speed)
                     STATE.update('test_download', speed, publish=0)
                     STATE.update('test_upload', 'N/A')
                 self._periodic_internal(stream)
                 context.state['complete'] = 1
                 stream.close()
                 return
         else:
             raise RuntimeError('skype_clnt: internal error')
     stream.recv(MAXRECV, self._waiting_piece)
Exemple #9
0
 def _waiting_piece(self, stream, data):
     """ Invoked when new data is available """
     # Note: this loop cannot be adapted to process other messages
     # easily, as pointed out in <raw_defs.py>.
     context = stream.opaque
     context.bufferise(data)
     context.state["rcvr_data"].append((utils.ticks(), len(data)))
     while True:
         if context.left > 0:
             context.left = context.skip(context.left)
             if context.left > 0:
                 break
         elif context.left == 0:
             tmp = context.pullup(4)
             if not tmp:
                 break
             context.left, = struct.unpack("!I", tmp)
             if context.left > MAXRECV:
                 raise RuntimeError("raw_clnt: PIECE too large")
             if not context.ticks:
                 context.ticks = context.snap_ticks = utils.ticks()
                 context.count = context.snap_count = stream.bytes_in
                 context.snap_utime, context.snap_stime = os.times()[:2]
                 POLLER.sched(1, self._periodic, stream)
             if context.left == 0:
                 logging.debug("< {empty-message}")
                 logging.info("raw_clnt: raw goodput test... complete")
                 ticks = utils.ticks()
                 timediff = ticks - context.ticks
                 bytesdiff = stream.bytes_in - context.count
                 context.state["goodput"] = {"ticks": ticks, "bytesdiff": bytesdiff, "timediff": timediff}
                 if timediff > 1e-06:
                     speed = utils.speed_formatter(bytesdiff / timediff)
                     logging.info("raw_clnt: goodput: %s", speed)
                     STATE.update("test_progress", "100%", publish=False)
                     STATE.update("test_download", speed, publish=0)
                     STATE.update("test_upload", "N/A")
                 self._periodic_internal(stream)
                 context.state["complete"] = 1
                 stream.close()
                 return
         else:
             raise RuntimeError("raw_clnt: internal error")
     stream.recv(MAXRECV, self._waiting_piece)
Exemple #10
0
 def _periodic_internal(stream):
     ''' Periodically snap goodput (internal function) '''
     context = stream.opaque
     utime, stime = os.times()[:2]
     utimediff = utime - context.snap_utime
     stimediff = stime - context.snap_stime
     ticks = utils.ticks()
     timediff = ticks - context.snap_ticks
     bytesdiff = stream.bytes_in - context.snap_count
     context.state.setdefault('goodput_snap', []).append({
       'ticks': ticks, 'bytesdiff': bytesdiff, 'timediff': timediff,
       'utimediff': utimediff, 'stimediff': stimediff})
     logging.debug('skype_clnt: utime, stime = %f, %f', utime, stime)
     if timediff > 1e-06:
         speed = utils.speed_formatter(bytesdiff / timediff)
         logging.debug('skype_clnt: goodput_snap: %s', speed)
     context.snap_count = stream.bytes_in
     context.snap_ticks = ticks
     context.snap_utime = utime
     context.snap_stime = stime
Exemple #11
0
    def got_choke(self, stream):

        #
        # The download terminates when we recv CHOKE.
        # The code below is adapted from version 1
        # code in got_piece().
        #
        if self.version >= 2:
            logging.info('BitTorrent: download ... done')

            # Calculate speed
            xfered = stream.bytes_recv_tot - self.saved_bytes
            elapsed = utils.ticks() - self.saved_ticks
            self.dload_speed = xfered/elapsed

            # Properly terminate download
            self.state = SENT_NOT_INTERESTED
            stream.send_not_interested()

            download = utils.speed_formatter(self.dload_speed)
            logging.info('BitTorrent: download speed: %s', download)

            # To next state
            if not self.connector_side:
                self.complete(stream, self.dload_speed, self.rtt,
                              self.target_bytes)
            else:
                STATE.update("test_download", download)

            # We MUST NOT fallthru
            return

        #
        # We don't implement CHOKE and we cannot ignore it, since
        # that would violate the specification.  So we raise an
        # exception, which has the side effect that the connection
        # will be closed.
        #
        raise RuntimeError("Unexpected CHOKE message")
Exemple #12
0
 def _periodic_internal(stream):
     ''' Periodically snap goodput (internal function) '''
     context = stream.opaque
     utime, stime = os.times()[:2]
     utimediff = utime - context.snap_utime
     stimediff = stime - context.snap_stime
     ticks = utils.ticks()
     timediff = ticks - context.snap_ticks
     bytesdiff = stream.bytes_out - context.snap_count
     context.state.setdefault('goodput_snap', []).append({
       'ticks': ticks, 'bytesdiff': bytesdiff, 'timediff': timediff,
       'utimediff': utimediff, 'stimediff': stimediff})
     logging.debug('raw_srvr: utime, stime = %f, %f', utime, stime)
     if timediff > 1e-06:
         speed = utils.speed_formatter(bytesdiff / timediff)
         logging.debug('raw_srvr: goodput_snap: %s', speed)
     web100_snap = web100.web100_snap(web100.WEB100_HEADER,
       context.web100_dirname)
     web100_snap['ticks'] = ticks
     context.state.setdefault('web100_snap', []).append(web100_snap)
     context.snap_count = stream.bytes_out
     context.snap_ticks = ticks
     context.snap_utime = utime
     context.snap_stime = stime
Exemple #13
0
 def _piece_sent(self, stream):
     ''' Invoked when a message has been sent '''
     context = stream.opaque
     ticks = utils.ticks()
     if ticks - context.ticks < 10:
         stream.send(context.message, self._piece_sent)
         #logging.debug('> PIECE')
         return
     logging.info('raw_srvr: raw test... complete')
     ticks = utils.ticks()
     timediff = ticks - context.ticks
     bytesdiff = stream.bytes_out - context.count
     context.state['timestamp'] = utils.timestamp()
     context.state['goodput'] = {
                                 'ticks': ticks,
                                 'bytesdiff': bytesdiff,
                                 'timediff': timediff,
                                }
     if timediff > 1e-06:
         speed = utils.speed_formatter(bytesdiff / timediff)
         logging.info('raw_srvr: goodput: %s', speed)
     self._periodic_internal(stream)
     stream.send(EMPTY_MESSAGE, self._empty_message_sent)
     logging.debug('> {empty-message}')
Exemple #14
0
        else:
            data = ""
        return data

    def seek(self, offset=0, whence=0):
        pass

    def tell(self):
        return self.total


if __name__ == "__main__":
    begin = ticks()
    m = "A" * 32768
    arc4 = arcfour_new()
    count = 0

    try:
        while True:
            e = arc4.encrypt(m)
            count += len(m)
    except KeyboardInterrupt:
        sys.stdout.write("\n")

    end = ticks()
    speed = count / (end - begin)
    speed = speed_formatter(speed)

    sys.stdout.write(speed)
    sys.stdout.write("\n")
Exemple #15
0
    def got_response(self, stream, request, response):
        """ Invoked when we receive the response from the server """

        if response.code != "200":
            logging.warning("dash: invalid response: %s", response.code)
            stream.close()
            return

        new_ticks = utils.ticks()
        new_bytes = stream.bytes_recv_tot
        new_times = os.times()[:2]

        logging.debug("dash: got response - ticks %f, bytes %d, times %s", new_ticks, new_bytes, new_times)

        elapsed = new_ticks - self.saved_ticks
        received = new_bytes - self.saved_cnt
        delta_user_time = new_times[0] - self.saved_times[0]
        delta_sys_time = new_times[1] - self.saved_times[1]

        if elapsed < 0:
            raise RuntimeError("dash: clock going backwards")

        logging.debug(
            "dash: got response - elaps %f, rcvd %d, user %f, sys %f",
            elapsed,
            received,
            delta_user_time,
            delta_sys_time,
        )

        if self.parent:
            result = {
                "connect_time": self.rtts[0],
                "delta_user_time": delta_user_time,
                "delta_sys_time": delta_sys_time,
                "elapsed": elapsed,
                "elapsed_target": DASH_SECONDS,
                "internal_address": stream.myname[0],
                "iteration": self.iteration,
                "platform": sys.platform,
                "rate": self.rate_kbit,
                "real_address": self.parent.real_address,
                "received": received,
                "remote_address": stream.peername[0],
                "request_ticks": self.saved_ticks,
                "timestamp": utils.timestamp(),
                "uuid": self.conf.get("uuid"),
                "version": utils_version.NUMERIC_VERSION,
            }
            self.parent.append_result(result)

        self.iteration += 1

        #
        # TODO it would be nice to also STATE.update() with the dash
        # rate, but that change requires also some www changes.
        #

        STATE.update("test_progress", "%d%%" % ((100 * self.iteration) / DASH_MAX_ITERATION), publish=False)

        speed = received / elapsed
        self.speed_kbit = (speed * 8) / 1000

        STATE.update("test_download", utils.speed_formatter(speed))
        logging.info(
            "dash: [%2d/%d] rate: %6d Kbit/s, speed: %6d Kbit/s, elapsed: %.3f s",
            self.iteration,
            DASH_MAX_ITERATION,
            self.rate_kbit,
            self.speed_kbit,
            elapsed,
        )

        if self.iteration >= DASH_MAX_ITERATION:
            logging.debug("dash: done all iteration")
            stream.close()
            return

        #
        # If we're adding too much delay, artificially reduce the
        # measured speed to let the bottleneck breathe.
        #
        if elapsed > DASH_SECONDS:
            rel_err = 1 - elapsed / DASH_SECONDS
            self.speed_kbit += rel_err * self.speed_kbit
            if self.speed_kbit < 0:
                self.speed_kbit = 100

        self.connection_ready(stream)
Exemple #16
0
    def update(self):
        if self.finished:
            return

        #
        # Decide whether we can transition to the next phase of
        # the speedtest or not.  Fall through to next request if
        # needed, or return to the caller and rewind the stack.
        #

        ostate = self.state

        if not self.state:
            self.state = "negotiate"
            del QUEUE_HISTORY[:]

        elif self.state == "negotiate":
            if self.conf.get("speedtest.client.unchoked", False):
                logging.info("* speedtest: %s ... authorized to " "take the test\n", self.state)
                self.state = "latency"
            elif "speedtest.client.queuepos" in self.conf:
                queuepos = self.conf["speedtest.client.queuepos"]
                logging.info("* speedtest: %s ... waiting in queue, " "pos %s\n", self.state, queuepos)
                STATE.update("negotiate", {"queue_pos": queuepos})
                QUEUE_HISTORY.append(queuepos)

        elif self.state == "latency":
            tries = self.conf.get("speedtest.client.latency_tries", 10)
            if tries == 0:
                # Calculate average latency
                latency = self.conf["speedtest.client.latency"]
                latency = sum(latency) / len(latency)
                self.conf["speedtest.client.latency"] = latency
                # Advertise the result
                STATE.update("test_progress", "33%", publish=False)
                STATE.update("test_latency", utils.time_formatter(latency))
                logging.info("* speedtest: %s ...  done, %s\n", self.state, utils.time_formatter(latency))
                self.state = "download"
            else:
                self.conf["speedtest.client.latency_tries"] = tries - 1

        elif self.state in ("download", "upload"):
            if len(self.streams) == self.conf.get("speedtest.client.nconn", 1):

                # Calculate average speed
                speed = self.conf["speedtest.client.%s" % self.state]
                elapsed = max(map(lambda t: t[1], speed)) - min(map(lambda t: t[0], speed))
                speed = sum(map(lambda t: t[2], speed)) / elapsed

                #
                # O(N) loopless adaptation to the channel w/ memory
                # TODO bittorrent/peer.py implements an enhanced version
                # of this algorithm, with a cap to the max number of
                # subsequent tests.  In addition to that, the bittorrent
                # code also anticipates the update of target_bytes.
                #
                if elapsed > LO_THRESH:
                    ESTIMATE[self.state] *= TARGET / elapsed
                    self.conf["speedtest.client.%s" % self.state] = speed
                    # Advertise
                    STATE.update("test_%s" % self.state, utils.speed_formatter(speed), publish=False)
                    logging.info("* speedtest: %s ...  done, %s\n", self.state, utils.speed_formatter(speed))
                    if self.state == "download":
                        STATE.update("test_progress", "66%")
                        self.state = "upload"
                    else:
                        STATE.update("test_progress", "100%")
                        self.state = "collect"
                elif elapsed > LO_THRESH / 3:
                    del self.conf["speedtest.client.%s" % self.state]
                    ESTIMATE[self.state] *= TARGET / elapsed
                else:
                    del self.conf["speedtest.client.%s" % self.state]
                    ESTIMATE[self.state] *= 2

            else:
                # Wait for all pending requests to complete
                return

        elif self.state == "collect":
            logging.info("* speedtest: %s ... done\n", self.state)
            self.cleanup()
            return

        else:
            raise RuntimeError("Invalid state")

        #
        # Perform state transition and run the next phase of the
        # speedtest.  Not all phases need to employ all the connection
        # with the upstream server.
        #

        if self.state == "negotiate":
            ctor, justone = ClientNegotiate, True
        elif self.state == "latency":
            ctor, justone = ClientLatency, True
        elif self.state == "download":
            ctor, justone = ClientDownload, False
        elif self.state == "upload":
            ctor, justone = ClientUpload, False
        elif self.state == "collect":
            ctor, justone = ClientCollect, True
        else:
            raise RuntimeError("Invalid state")

        if ostate != self.state:
            self.child = ctor(self.poller)
            self.child.configure(self.conf)
            self.child.host_header = self.host_header
            if self.state not in ("negotiate", "collect"):
                if ostate == "negotiate" and self.state == "latency":
                    STATE.update("test", "speedtest")
            else:
                STATE.update(self.state)
            logging.info("* speedtest: %s in progress...", self.state)
        elif self.state == "negotiate":
            logging.info("* speedtest: %s in progress...", self.state)

        while self.streams:
            #
            # Override child Time-To-Connect (TTC) with our TTC
            # so for the child it's like it really performed the
            # connect(), not us.
            #
            self.child.rtts = self.rtts
            self.child.connection_ready(self.streams.popleft())
            if justone:
                break
Exemple #17
0
    def get_piece_old(self, stream):
        ''' implements get_piece() for test version 1 '''

        # Get next piece
        try:
            vector = self.sched_req.next()
        except StopIteration:
            vector = None

        if vector:
            # Send next piece
            index, begin, length = vector[0]
            stream.send_request(index, begin, length)

        else:
            #
            # No more pieces: Wait for the pipeline to empty
            #
            # TODO Check whether it's better to stop the measurement
            # when the pipeline starts emptying instead of when it
            # becomes empty (maybe it is reasonable to discard when
            # it fills and when it empties, isn't it?)
            #
            self.inflight -= 1
            if self.inflight == 0:
                xfered = stream.bytes_recv_tot - self.saved_bytes
                elapsed = utils.ticks() - self.saved_ticks
                speed = xfered/elapsed

                logging.info("BitTorrent: downloading %d bytes ... %s",
                  self.target_bytes, utils.speed_formatter(speed))

                #
                # Make sure that next test would take about
                # TARGET secs, under current conditions.
                # We're a bit conservative when the elapsed
                # time is small because there is the risk of
                # overestimating the available bandwith.
                # TODO Don't start from scratch but use speedtest
                # estimate (maybe we need to divide it by two
                # but I'm not sure at the moment).
                #
                if elapsed >= LO_THRESH/3:
                    self.target_bytes = int(self.target_bytes * TARGET/elapsed)
                else:
                    self.target_bytes *= 2

                #
                # The stopping rule is when the test has run
                # for more than LO_THRESH seconds or after some
                # number of runs (just to be sure that we can
                # not run forever due to unexpected network
                # conditions).
                #
                self.repeat -= 1
                if elapsed > LO_THRESH or self.repeat <= 0:
                    self.dload_speed = speed
                    self.state = SENT_NOT_INTERESTED
                    stream.send_not_interested()
                    if not self.connector_side:
                        self.complete(stream, self.dload_speed, self.rtt,
                                      self.target_bytes)
                    else:
                        download = utils.speed_formatter(self.dload_speed)
                        STATE.update("test_progress", "50%", publish=False)
                        STATE.update("test_download", download)
                else:
                    self.saved_ticks = 0
                    self.make_sched()
                    self.state = SENT_INTERESTED        #XXX
                    self.got_unchoke(stream)

            elif self.inflight < 0:
                raise RuntimeError("Inflight became negative")