18

Let's say I have a makefile with a recipe called hour_long_recipe, which as its name suggests takes an hour to run. At random points throughout the recipe it asks yes/no questions. Let's say it asks 10 questions in total.

One possible (and often recommended) way to run it is:

yes | make hour_long_recipe

which answers all questions with y. However, from my understanding, yes keeps outputting to its stdout at up to 10.2 GiB per second regardless of whether make is actually using that data from its stdin.

Even if it was just 10 MiB/s (much slower than any implementation of yes if that reddit thread is to be believed), during the course of an hour it would add up to over 35 GiB, of which just 20 bytes will be read. Where does the data go? It's possible to save it to disk but that's wasteful, and if the disk fills up fast enough it can even cause make to fail.

Presumably the operating system will stop it from getting to that, but how? What is the limit, and what happens when that limit is reached?

NeatNit
  • 291
  • 2
  • 6
  • 4
    That's a good idea if this was actually something I needed to do, but this question is about understanding how Linux piping works and whether yes will crash or spam your disk in such a situation – NeatNit Dec 31 '20 at 13:52
  • 1
    @muru it does, but it took me until fully writing this question (and then some) to figure out that that's the question I probably should have been asking. – NeatNit Dec 31 '20 at 15:16
  • 4
    to clarify, this question isn't "how big is the pipe", but rather "what happens when trying to write boatloads of data to a pipe". The fact that a pipe has a maximum size at all is the answer, together with an explanation of what happens when that size is full. Chris's answer is perfect, and includes info not found in the other question you linked. – NeatNit Dec 31 '20 at 15:32
  • That's still what you're asking at the end of the question, though. – muru Dec 31 '20 at 23:21
  • up to 10.2 GiB per second no, it may run faster in some computers or in the future. There's no limit in speed – phuclv Jan 01 '21 at 00:56
  • Related: https://what-if.xkcd.com/36/ – Franz Wimmer Jan 01 '21 at 09:06
  • Internal link for fast yes: https://codegolf.stackexchange.com/questions/199528/fastest-yes-in-the-west – Joshua Jan 01 '21 at 20:28
  • Disclosure: I wrote the accepted answer to Read until pipe is closed.  Suggestion: the last couple of paragraphs of my answer provide an answer to this question. – G-Man Says 'Reinstate Monica' Jan 04 '21 at 08:20
  • 1
    @G-ManSays'ReinstateMonica' I don't see how my question is remotely a "duplicate" of that question. The question you linked is very convoluted and stems from a whole bunch of misconceptions and confusion, and the answer is appropriately also complex and covers many topics related to pipes. My question is not addressed, at least not obviously or directly. My question is (I think) concise and specific, with a single correct answer. How is that a duplicate? – NeatNit Jan 04 '21 at 13:38
  • And @muru the question you marked is also not a duplicate. My question is not aware that there is a size to the pipe, your linked question is asking how big it is. Not the same question. – NeatNit Jan 04 '21 at 13:41
  • It depends on, how yes writes its output. If it is blocking IO, then no, yes will be suspended until make does not read out its output. If it is non-blocking IO, then its write(...) calls will return -EAGAIN, essentially a "wait and send later again" warning. make does not read from its stdin, but the sub-processes what it starts, might read it, and they start with the stdin what they inherited from the make. – peterh Jan 08 '21 at 23:33

1 Answers1

35

tl;dr: at some point, yes will be blocked from writing if the data isn't being read on the other side. It will not be able to continue executing until either that data is read, or it receives a signal, so you typically don't need to worry about yes writing gigabytes and gigabytes of data.


The important thing to remember is that a pipe is a FIFO data structure, not simply a pure stream which drops data if not immediately read on the receiver. That is, while it may appear in most cases to be a seamless stream of data from the writing application to the reading application, it does require intermediate storage to perform that, and that intermediate storage is of finite size.*

If we look at the pipe(7) man page, we can read the following about the size of that internal buffer (emphasis added):

In Linux versions before 2.6.11, the capacity of a pipe was the same as the system page size (e.g., 4096 bytes on i386). Since Linux 2.6.11, the pipe capacity is 16 pages (i.e., 65,536 bytes in a system with a page size of 4096 bytes). Since Linux 2.6.35, the default pipe capacity is 16 pages, but the capacity can be queried and set using the fcntl(2) F_GETPIPE_SZ and F_SETPIPE_SZ operations.

Assuming you're using a standard x86_64 system, it's very likely that you use 4KiB pages, so the 2^16 upper limit on pipe capacity likely is correct unless either side of the pipeline at some point used fcntl(F_SETPIPE_SZ). Either way, the principle stands: the intermediate storage between two sides of a pipe is finite, and is stored in memory.

In an abstract pipeline a | b, this storage is used in the period between a writing some data, and b actually reading it. Assuming, then, that your make invocation (and any children also connected to this pipe by inheritance) don't actually try to read stdin, or only do so sparingly, the write syscall from yes will eventually simply not wake up yes from sleep when buffer space is exhausted. yes will then wait to be woken up, either when buffer space is available again, or a signal is received.** All of this is handled by the kernel's process scheduler. You can see this in pipe_write(), which is the write() handler for pipes:

static ssize_t
pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
    /* ... */
if (pipe_full(pipe->head, pipe->tail, pipe->max_usage))
    wake_next_writer = false;

if (wake_next_writer)
    wake_up_interruptible_sync_poll(&pipe->wr_wait, EPOLLOUT | EPOLLWRNORM);

/* ... */

}

When the make side eventually terminates, yes will be sent SIGPIPE as a result of writing to a pipe with nothing remaining on the other end. This will then — depending on yes implementation — invoke either its own signal handler or the default kernel signal handler, and it will terminate.***


* In simple circumstances, where the receiver is processing the data at roughly the same rate that it's being written, this transfer can also be zero-copy with no intermediate buffer by using virtual memory to map and make available the physical page from the writing process available to the receiver. However, the case you're describing will certainly eventually need to use the pipe buffer to store the unread data.

** It's also possible that the writing is done with the O_NONBLOCK flag set on the file descriptor, which enables non-blocking mode. In this case, you'll probably get one incomplete write, and then write will return EAGAIN and the application will need to deal with that itself. It will likely either do that by suspending or running some other code of its choosing to handle the pipe being full. In the case of every modern yes version I can find and most other applications, though, the description above is what happens, since they don't use O_NONBLOCK.

*** An application can do whatever it likes upon receiving SIGPIPE -- it may even theoretically decide not to terminate. However, all common yes use the default SIGPIPE handler, which just terminates without executing any more userspace instructions.

Chris Down
  • 125,559
  • 25
  • 270
  • 266
  • 2
    That's in essence the same as a real life pipe. – Stéphane Chazelas Dec 31 '20 at 19:36
  • 8
    The technical term for this is "blocking". write blocks when the pipe's buffer is full, and doesn't return until the reader has reduced the buffer contents sufficiently for write to store all the data it was given in the buffer. – cjm Jan 01 '21 at 03:44
  • 1
    Another related technical term is "flow control". And yes, blocking the writer is part of the implementation of how flow control works on a pipe. (Though in general, if the writer is something other than yes, it might also be possible that the writer sets its end of the pipe to O_NONBLOCK mode, and then integrates it into a select/poll/epoll/... loop.) – Daniel Schepler Jan 01 '21 at 21:03
  • Do you know of any yes implementation which installs its own SIGPIPE signal handler? If not, that statement is both superfluous and incomplete, because a hyphothetical yes implementation could also ignore the SIGPIPE signal and handle (or just error out on) the errno = EPIPE from write(2) (as python does). –  Jan 11 '21 at 15:38
  • @user414777 yes isn't standardised, so sure, you can write any kind of behaviour you want. I don't really have much interest in getting into semantic hypotheticals, but it's not farfetched to believe that there may be some yes which does some kind of teardown prior to terminating. It is, of course, possible to create a yes which doesn't operate at all like a "normal yes" (ie. GNU, BSD, popular historic Unices), but at that point this discussion ceases to have any real meaning. But sure, I can add a clarification that this is not enforced by the kernel. – Chris Down Jan 11 '21 at 17:35
  • that was not my point -- I was actually suggesting that you shorten the last statement to just "This [the SIGPIPE] will then cause yes to terminate", instead of going off on that tangent about installing its own signal handler (which does not happen and would be completely unpractical -- there's very little cleanup you can safely do from a signal handler, etc; most programs who want to catch a "broken pipe" condition set SIGPIPE to SIG_IGN and handle the errno = EPIPE error instead). –  Jan 11 '21 at 19:12
  • @user414777 Unless I'm misunderstanding you, my answer doesn't suggest that any cleanup would happen inside a signal handler, just that a signal handler may be invoked as part of that process. You typically wouldn't do any cleanup inside the signal handler, you'd just set a sig_atomic_t and do it elsewhere. Again: we're discussing a purely abstract, hypothetical yes implementation here, but there's no reason that wouldn't be equally as reasonable as looking explicitly for EPIPE if some cleanup was needed, depending on what cleanup it was. – Chris Down Jan 11 '21 at 20:11
  • (It should also be mentioned that this question is more generic than it was when it was first written, and now generically asks "what happens when writing gigabytes of data to a pipe" instead of "how does piping yes work". That also somewhat influenced the direction of this answer to being something more generic than just a pure description of GNU yes's behaviour.) – Chris Down Jan 11 '21 at 20:22
  • In any program, there's absolutely zero reason to set up a SIGPIPE handler and then try to "extract" the "broken pipe" condition from the errno = EINTR after a blocking write(2) has returned -1, instead of just ignoring SIGPIPE and handling errno = EPIPE. But I'll cut it short here, since it seems that I'm not able to get my point accross. Anyway, if any reader of this is wondering how to know when a pipe was broken without trying to write to it (e.g. when using non-blocking i/o), see the last part of this –  Jan 12 '21 at 04:17