mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-06 08:51:14 -03:00
On process exit, read output into buffer to fix ordering
This command
echo $(/bin/echo -n 1; echo -n 2)
sometimes outputs "21" because we implement this as
let bufferfill = IoBufferfill::create_opts(...);
...
let eval_res = parser.eval_with(...);
let buffer = IoBufferfill::finish(bufferfill);
i.e. /bin/echo and builtin echo both output to the same buffer; the
builtin does inside parser.eval_with(), and the external process may
or may not output before that, depending on when the FD monitor thread
gets scheduled (to run item_callback).
(Unrelated to that we make sure to consume all available input in
"IoBufferfill::finish(bufferfill)" but that doesn't help with
ordering.)
Fix this by reading all available data from stdout after the child
process has exited.
This means we need to pass the BufferFill down to
process_mark_finished_children().
We don't need to do this for builtins like "fg" or "wait",
because commands that buffer output do not get job control, see
2ca66cff53 (Disable job control inside command substitutions,
2021-07-26).
We also don't need to do it when reaping from reader because there
should be no buffering(?).
fish still deviates from other shells in that it doesn't wait for
it's child's stdout to be closed, meaning that this will behave
non-deterministically.
fish -c '
echo -n $(
sh -c " ( for i in \$(seq 10000); do printf .; done ) & "
)
' | wc -c
We should fix that later.
Closes #12018
This commit is contained in:
26
src/io.rs
26
src/io.rs
@@ -355,9 +355,13 @@ pub fn buffer(&self) -> &IoBuffer {
|
||||
&self.buffer
|
||||
}
|
||||
|
||||
pub fn read_all_available(&self) {
|
||||
fd_monitor().with_fd(self.item_id, |fd| self.buffer.read_all_available(fd));
|
||||
}
|
||||
|
||||
/// Reset the receiver (possibly closing the write end of the pipe), and complete the fillthread
|
||||
/// of the buffer. Return the buffer.
|
||||
pub fn finish(filler: Arc<IoBufferfill>) -> SeparatedBuffer {
|
||||
pub fn finish(filler: Arc<Self>) -> SeparatedBuffer {
|
||||
// The io filler is passed in. This typically holds the only instance of the write side of the
|
||||
// pipe used by the buffer's fillthread (except for that side held by other processes).
|
||||
// Then allow the buffer to finish.
|
||||
@@ -435,14 +439,28 @@ pub fn read_once(fd: RawFd, buffer: &mut MutexGuard<'_, SeparatedBuffer>) -> isi
|
||||
amt
|
||||
}
|
||||
|
||||
pub fn read_all_available(&self, fd: &AutoCloseFd) {
|
||||
let mut locked_buff = self.0.lock().unwrap();
|
||||
self.do_read_all_available(fd, &mut locked_buff);
|
||||
}
|
||||
|
||||
fn do_read_all_available(
|
||||
&self,
|
||||
fd: &AutoCloseFd,
|
||||
locked_buff: &mut MutexGuard<'_, SeparatedBuffer>,
|
||||
) {
|
||||
// Read any remaining data from the pipe.
|
||||
while fd.is_valid() && IoBuffer::read_once(fd.as_raw_fd(), &mut *locked_buff) > 0 {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
/// End the background fillthread operation, and return the buffer, transferring ownership.
|
||||
/// The read end of the pipe is provided.
|
||||
pub fn complete_and_take_buffer(&self, fd: AutoCloseFd) -> SeparatedBuffer {
|
||||
// Read any remaining data from the pipe.
|
||||
let mut locked_buff = self.0.lock().unwrap();
|
||||
while fd.is_valid() && IoBuffer::read_once(fd.as_raw_fd(), &mut locked_buff) > 0 {
|
||||
// pass
|
||||
}
|
||||
self.do_read_all_available(&fd, &mut locked_buff);
|
||||
|
||||
// Return our buffer, transferring ownership.
|
||||
let mut result = SeparatedBuffer::new(locked_buff.limit());
|
||||
|
||||
Reference in New Issue
Block a user