Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the "yes | sleep 10" pipe not fail

Tags:

bash

pipe

In thinking about how to implement a certain feature in one of my own programs I've been wondering how bash handles pipes of the following nature internally:

yes | sleep 10

This obviously does nothing but I don't understand how this does not result in an error. I would have thought that either:

  • because sleep does not read from stdin, the pipe connecting both processes would fill up and cause yes to block indefinitely when it attempts to write to the now full pipe

  • if non-blocking IO is used, errors should occur if yes is executed first and writes to the pipe before the sleep process is even run and thus no process is connected to the read end of the pipe

I guess this is some major misunderstanding on my part. I've tried looking at the bash source code but that's gone over my head.

like image 372
Peter Avatar asked Sep 22 '18 12:09

Peter


1 Answers

Here's what actually happens when you run the shell command yes | sleep 10.

First the shell creates an anonymous pipe using the pipe system call. The pipe system call opens two file descriptors which are the read end and the write end of the pipe. Whatever gets written to the write end becomes available for reading from the read end.

After this, the shell creates two child processes using the fork system call. The two children run in parallel.

  • In one child, the shell connects the write end of the pipe to standard output and closes the read end. Then the shell calls the execve system call to replace the code image in this process by the code image of yes.
    The program yes writes to the pipe as long as it can. If there isn't an active read call on the read end of the pipe, the write call just blocks. (There's actually a small buffer which write will fill up before blocking, but this doesn't matter here.)
  • In the other child, the shell connects the read end of the pipe to standard input and closes the write end. Then the shell calls the execve system call to replace the code image in this process by the code image of sleep. The program sleep does nothing for 10 seconds.
  • The original shell process closes both ends of the pipe and waits for both of its children to exit (using the wait system call).

Once the 10 seconds are up, the process running sleep exits. At this point, the read end of the pipe is no longer open in any process. When a process tries to write to a pipe whose read end is not open in any process, the kernel sends a SIGPIPE signal to the writing process. Thus the process running yes is killed by the SIGPIPE signal.

At this point, the shell detects that its child processes on both sides of the pipeline have exited. The pipeline command returns the status of the right-hand side, which is 0 (sleep exits successfully).

because sleep does not read from stdin, the pipe connecting both processes would fill up and cause yes to block indefinitely when it attempts to write to the now full pipe

This is correct.

if non-blocking IO is used, errors should occur if yes is executed first and writes to the pipe before the sleep process is even run and thus no process is connected to the read end of the pipe

This is not correct in several places. yes does not use non-blocking IO. It's executed in parallel with sleep, not first. There is never any point in time when no process is connected to the read end of the pipe, not until sleep exits. Depending on the timing, it's possible that yes will start writing before sleep starts executing, maybe even before the child process for the sleep program is forked, but the read end became open when the pipe call returned, at the same time as the write end became open.

like image 197
Gilles 'SO- stop being evil' Avatar answered Nov 18 '22 14:11

Gilles 'SO- stop being evil'