view rust/rhg/src/ui.rs @ 45095:8e04607023e5

procutil: ensure that procutil.std{out,err}.write() writes all bytes Python 3 offers different kind of streams and it’s not guaranteed for all of them that calling write() writes all bytes. When Python is started in unbuffered mode, sys.std{out,err}.buffer are instances of io.FileIO, whose write() can write less bytes for platform-specific reasons (e.g. Linux has a 0x7ffff000 bytes maximum and could write less if interrupted by a signal; when writing to Windows consoles, it’s limited to 32767 bytes to avoid the "not enough space" error). This can lead to silent loss of data, both when using sys.std{out,err}.buffer (which may in fact not be a buffered stream) and when using the text streams sys.std{out,err} (I’ve created a CPython bug report for that: https://bugs.python.org/issue41221). Python may fix the problem at some point. For now, we implement our own wrapper for procutil.std{out,err} that calls the raw stream’s write() method until all bytes have been written. We don’t use sys.std{out,err} for larger writes, so I think it’s not worth the effort to patch them.
author Manuel Jacob <me@manueljacob.de>
date Fri, 10 Jul 2020 12:27:58 +0200
parents 513b3ef277a3
children eb55274d3650
line wrap: on
line source

use std::io;
use std::io::Write;

pub struct Ui {}

/// The kind of user interface error
pub enum UiError {
    /// The standard output stream cannot be written to
    StdoutError(io::Error),
    /// The standard error stream cannot be written to
    StderrError(io::Error),
}

/// The commandline user interface
impl Ui {
    pub fn new() -> Self {
        Ui {}
    }

    /// Write bytes to stdout
    pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
        let mut stdout = io::stdout();

        self.write_stream(&mut stdout, bytes)
            .or_else(|e| self.into_stdout_error(e))?;

        stdout.flush().or_else(|e| self.into_stdout_error(e))
    }

    fn into_stdout_error(&self, error: io::Error) -> Result<(), UiError> {
        self.write_stderr(
            &[b"abort: ", error.to_string().as_bytes(), b"\n"].concat(),
        )?;
        Err(UiError::StdoutError(error))
    }

    /// Write bytes to stderr
    pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
        let mut stderr = io::stderr();

        self.write_stream(&mut stderr, bytes)
            .or_else(|e| Err(UiError::StderrError(e)))?;

        stderr.flush().or_else(|e| Err(UiError::StderrError(e)))
    }

    fn write_stream(
        &self,
        stream: &mut impl Write,
        bytes: &[u8],
    ) -> Result<(), io::Error> {
        stream.write_all(bytes)
    }
}