A read can block for an unbounded amount of time, even if the
            kernel reports that the pipe handle is signalled, so we need to
            always perform our reads asynchronously

def readBytes(self, size):
        """ A read can block for an unbounded amount of time, even if the
            kernel reports that the pipe handle is signalled, so we need to
            always perform our reads asynchronously
        """

        # try to satisfy the read from any buffered data
        if self._iobuf:
            if size >= len(self._iobuf):
                res = self._iobuf
                self.buf = None
                return res
            res = self._iobuf[:size]
            self._iobuf = self._iobuf[size:]
            return res

        # We need to initiate a read
        buf = ctypes.create_string_buffer(size)
        olap = OVERLAPPED()
        olap.hEvent = self._waitable

        log("made read buff of size %d", size)

        # ReadFile docs warn against sending in the nread parameter for async
        # operations, so we always collect it via GetOverlappedResultEx
        immediate = ReadFile(self.pipe, buf, size, None, olap)

        if not immediate:
            err = GetLastError()
            if err != ERROR_IO_PENDING:
                self._raise_win_err("failed to read %d bytes" % size, err)

        nread = wintypes.DWORD()
        if not self._get_overlapped_result_ex(
            self.pipe, olap, nread, 0 if immediate else self.timeout, True
        ):
            err = GetLastError()
            CancelIoEx(self.pipe, olap)

            if err == WAIT_TIMEOUT:
                log("GetOverlappedResultEx timedout")
                raise SocketTimeout(
                    "timed out after waiting %dms for read" % self.timeout
                )

            log("GetOverlappedResultEx reports error %d", err)
            self._raise_win_err("error while waiting for read", err)

        nread = nread.value
        if nread == 0:
            # Docs say that named pipes return 0 byte when the other end did
            # a zero byte write.  Since we don't ever do that, the only
            # other way this shows up is if the client has gotten in a weird
            # state, so let's bail out
            CancelIoEx(self.pipe, olap)
            raise IOError("Async read yielded 0 bytes; unpossible!")

        # Holds precisely the bytes that we read from the prior request
        buf = buf[:nread]

        returned_size = min(nread, size)
        if returned_size == nread:
            return buf

        # keep any left-overs around for a later read to consume
        self._iobuf = buf[returned_size:]
        return buf[:returned_size]