this function is run in a separate thread.  it reads from the
    process's stdout stream (a streamreader), and waits for it to claim that
    its done

def output_thread(log, stdout, stderr, timeout_event, is_alive, quit,
        stop_output_event):
    """ this function is run in a separate thread.  it reads from the
    process's stdout stream (a streamreader), and waits for it to claim that
    its done """

    poller = Poller()
    if stdout is not None:
        poller.register_read(stdout)
    if stderr is not None:
        poller.register_read(stderr)

    # this is our poll loop for polling stdout or stderr that is ready to
    # be read and processed.  if one of those streamreaders indicate that it
    # is done altogether being read from, we remove it from our list of
    # things to poll.  when no more things are left to poll, we leave this
    # loop and clean up
    while poller:
        changed = no_interrupt(poller.poll, 0.1)
        for f, events in changed:
            if events & (POLLER_EVENT_READ | POLLER_EVENT_HUP):
                log.debug("%r ready to be read from", f)
                done = f.read()
                if done:
                    poller.unregister(f)
            elif events & POLLER_EVENT_ERROR:
                # for some reason, we have to just ignore streams that have had an
                # error.  i'm not exactly sure why, but don't remove this until we
                # figure that out, and create a test for it
                pass

        if timeout_event and timeout_event.is_set():
            break

        if stop_output_event.is_set():
            break

    # we need to wait until the process is guaranteed dead before closing our
    # outputs, otherwise SIGPIPE
    alive, _ = is_alive()
    while alive:
        quit.wait(1)
        alive, _ = is_alive()

    if stdout:
        stdout.close()

    if stderr:
        stderr.close()