import time


def blocking_read(istream, size=-1, nonblocking_reads_delay=0.06):
    """
    Read from *istream* and do not return until *size* `bytes` have been read
    unless EOF has been reached.

    Return all the data read so far. The length of the returned data may still
    be less than *size* in case EOF has been reached.

    *nonblocking_reads_delay* specifies the number of seconds (float) to wait
    before trying to read from *istream* again in case `BlockingIOError` has
    been raised during previous call.
    """
    assert size is None or (isinstance(size, int) and not isinstance(size, bool))

    if not size:
        return None

    output = []
    len_read = 0

    def _join():
        glue = '' if isinstance(output[0], str) else b''
        return glue.join(output)

    while True:
        try:
            chunk = istream.read(-1 if size < 0 else size - len_read)

            if size < 0:
                return chunk
            elif not chunk:
                if not output:
                    return chunk
                else:
                    return _join()
            else:
                assert size > 0
                output.append(chunk)
                len_read += len(chunk)
                if len_read >= size:
                    return _join()
        except BlockingIOError:
            time.sleep(nonblocking_reads_delay)