import concurrent.futures as fut


def future_results(it, max_active):
    """Given a generator of future objects return a generator of result tuples.

    Result Tuple contains:
     (fut.result(), None) -- if future succeeded
     (None, fut.exception()) -- if future failed


    This is roughly equivalent to:

        ((f.result(), f.exception()) for f in it)

    except that there are upto max_active futures that are active at any given
    time, and also order of result is "as completed". So it's like
    concurrent.futures.as_completed, but with upper bound on the number of
    active futures rather than a timeout.
    """

    def result(f):
        err = f.exception()
        if err is not None:
            return (None, err)
        else:
            return (f.result(), None)

    def fill(src, n, dst):
        while n > 0:
            try:
                x = next(src)
            except StopIteration:
                return False

            dst.add(x)
            n -= 1
        return True

    max_fill = 10
    active = set()
    have_more = True

    try:
        while have_more:
            need_n = min(max_fill, max_active - len(active))
            have_more = fill(it, need_n, active)

            # blocking wait if active count is reached
            timeout = None if len(active) >= max_active else 0.01

            xx = fut.wait(active, timeout=timeout, return_when="FIRST_COMPLETED")
            active = xx.not_done

            for f in xx.done:
                yield result(f)
                if have_more:
                    have_more = fill(it, 1, active)

        # all futures were issued: do the flush now
        for f in fut.as_completed(active):
            yield result(f)
    finally:
        for f in active:
            f.cancel()