def _writer_daemon()

in lib/ramble/llnl/util/tty/log.py [0:0]


def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo,
                   log_file_wrapper, control_pipe, filter_fn):
    """Daemon used by ``log_output`` to write to a log file and to ``stdout``.

    The daemon receives output from the parent process and writes it both
    to a log and, optionally, to ``stdout``.  The relationship looks like
    this::

        Terminal
           |
           |          +-------------------------+
           |          | Parent Process          |
           +--------> |   with log_output():    |
           | stdin    |     ...                 |
           |          +-------------------------+
           |            ^             | write_fd (parent's redirected stdout)
           |            | control     |
           |            | pipe        |
           |            |             v read_fd
           |          +-------------------------+   stdout
           |          | Writer daemon           |------------>
           +--------> |   read from read_fd     |   log_file
             stdin    |   write to out and log  |------------>
                      +-------------------------+

    Within the ``log_output`` handler, the parent's output is redirected
    to a pipe from which the daemon reads.  The daemon writes each line
    from the pipe to a log file and (optionally) to ``stdout``.  The user
    can hit ``v`` to toggle output on ``stdout``.

    In addition to the input and output file descriptors, the daemon
    interacts with the parent via ``control_pipe``.  It reports whether
    ``stdout`` was enabled or disabled when it finished and, if the
    ``log_file`` is a ``StringIO`` object, then the daemon also sends the
    logged output back to the parent as a string, to be written to the
    ``StringIO`` in the parent. This is mainly for testing.

    Arguments:
        stdin_multiprocess_fd (int): input from the terminal
        read_multiprocess_fd (int): pipe for reading from parent's redirected
            stdout
        echo (bool): initial echo setting -- controlled by user and
            preserved across multiple writer daemons
        log_file_wrapper (FileWrapper): file to log all output
        control_pipe (Pipe): multiprocessing pipe on which to send control
            information to the parent
        filter_fn (callable, optional): function to filter each line of output

    """
    # If this process was forked, then it will inherit file descriptors from
    # the parent process. This process depends on closing all instances of
    # write_fd to terminate the reading loop, so we close the file descriptor
    # here. Forking is the process spawning method everywhere except Mac OS
    # for Python >= 3.8 and on Windows
    if sys.version_info < (3, 8) or sys.platform != 'darwin':
        os.close(write_fd)

    # Use line buffering (3rd param = 1) since Python 3 has a bug
    # that prevents unbuffered text I/O.
    if sys.version_info < (3,):
        in_pipe = os.fdopen(read_multiprocess_fd.fd, 'r', 1)
    else:
        # Python 3.x before 3.7 does not open with UTF-8 encoding by default
        in_pipe = os.fdopen(read_multiprocess_fd.fd, 'r', 1, encoding='utf-8', closefd=False)

    if stdin_multiprocess_fd:
        stdin = os.fdopen(stdin_multiprocess_fd.fd, closefd=False)
    else:
        stdin = None

    # list of streams to select from
    istreams = [in_pipe, stdin] if stdin else [in_pipe]
    force_echo = False      # parent can force echo for certain output

    log_file = log_file_wrapper.unwrap()

    try:
        with keyboard_input(stdin) as kb:
            while True:
                # fix the terminal settings if we recently came to
                # the foreground
                kb.check_fg_bg()

                # wait for input from any stream. use a coarse timeout to
                # allow other checks while we wait for input
                rlist, _, _ = _retry(select.select)(istreams, [], [], 1e-1)

                # Allow user to toggle echo with 'v' key.
                # Currently ignores other chars.
                # only read stdin if we're in the foreground
                if stdin in rlist and not _is_background_tty(stdin):
                    # it's possible to be backgrounded between the above
                    # check and the read, so we ignore SIGTTIN here.
                    with ignore_signal(signal.SIGTTIN):
                        try:
                            if stdin.read(1) == 'v':
                                echo = not echo
                        except IOError as e:
                            # If SIGTTIN is ignored, the system gives EIO
                            # to let the caller know the read failed b/c it
                            # was in the bg. Ignore that too.
                            if e.errno != errno.EIO:
                                raise

                if in_pipe in rlist:
                    line_count = 0
                    try:
                        while line_count < 100:
                            # Handle output from the calling process.
                            try:
                                line = _retry(in_pipe.readline)()
                            except UnicodeDecodeError:
                                # installs like --test=root gpgme produce non-UTF8 logs
                                line = '<line lost: output was not encoded as UTF-8>\n'

                            if not line:
                                return
                            line_count += 1

                            # find control characters and strip them.
                            clean_line, num_controls = control.subn('', line)

                            # Echo to stdout if requested or forced.
                            if echo or force_echo:
                                output_line = clean_line
                                if filter_fn:
                                    output_line = filter_fn(clean_line)
                                sys.stdout.write(output_line)

                            # Stripped output to log file.
                            log_file.write(_strip(clean_line))

                            if num_controls > 0:
                                controls = control.findall(line)
                                if xon in controls:
                                    force_echo = True
                                if xoff in controls:
                                    force_echo = False

                            if not _input_available(in_pipe):
                                break
                    finally:
                        if line_count > 0:
                            if echo or force_echo:
                                sys.stdout.flush()
                            log_file.flush()

    except BaseException:
        tty.error("Exception occurred in writer daemon!")
        traceback.print_exc()

    finally:
        # send written data back to parent if we used a StringIO
        if isinstance(log_file, io.StringIO):
            control_pipe.send(log_file.getvalue())
        log_file_wrapper.close()
        read_multiprocess_fd.close()
        if stdin_multiprocess_fd:
            stdin_multiprocess_fd.close()

        # send echo value back to the parent so it can be preserved.
        control_pipe.send(echo)