3

Sometimes, a command is stalled attempting to write to a FIFO/pipe that no other process is currently reading from, but typing Ctrl-Z to put the process to the background by sending it the SIGTSTP signal (i.e. the suspend non-printing control character ^Z) does work.

Example:

$ mkfifo p
$ ll p
prw-rw-r-- 1 me me 0 Jul 30 16:27 p|
$ echo "Go!" >p    # Here `&` has been forgotten after the `>p` redirection
(stalled)
[Ctrl-Z]
^Z                 # Pressing Ctrl-Z just prints "^Z" on the terminal, nothing else happens
[Ctrl-D]           # Attempting to send the EOF character
(nothing)          # Doesn't print "^D" and does nothing

and this is the behavior even though SIGTSTP (or susp) is reported to be attached to ^Z:

$ stty -a
speed 38400 baud; rows 24; columns 91; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>;
swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V;
discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl -ixon -ixoff -iuclc
-ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
-flusho -extproc

How comes that in this case the terminal does not catch and transmit to the process the SIGTSTP signal generated by the Ctrl-Z? Is it because redirecting with > to a FIFO/pipe puts the terminal in raw mode (cf. The TTY demystified)? But if so, why does "^Z" get printed on the screen, and why Ctrl-D (EOF) doesn't print anything and yet doesn't have any effect either?

Incidentally, is there an alternative way to send the stalled process to the background in such cases (instead of just terminate the process with Ctrl-C (i.e. ^C / SIGINT)?

1 Answer 1

6

echo is a builtin command in virtually all shells including bash.

In:

cmd > fifo

It's the opening of the fifo by the shell to perform the redirection that blocks.

If cmd is an external command, the shell forks itself first and does the opening in the child process. If builtin or a function or compound command, it does it in the current shell process.

Suspending that process would mean suspending the shell rendering it unusable so shells don't let you do that.

In other word, you can't send to the background something that doesn't run in a separate process (there are some exceptions with zsh, see below).

If you run:

/bin/echo 'Go!' > p

You'll find that you'll be able to suspend that process and see in ps that it's not running echo yet, it's still a bash child process still trying to open the fifo.

Same with:

(echo 'Go!') > p

Or:

(echo 'Go!' > p)

Where we explicitly ask for a subshell (in which we run the builtin echo).

The zsh shell supports suspending functions and compound commands in general. Upon a Ctrl+z while running the function, it forks itself, with the child process running (initially stopped) the rest of the function, but that only happens when code in the function is waiting for a child process, so even there, that wouldn't help, that open() of the fifo would still be immune to SIGTSTP if done in the main shell process, the visible different compared to bash would be for instance in:

{ (echo 'Go!' > p); echo done; }

Where upon processing Ctrl+z, in bash, you see the done straight away and only the (...) subshell is put in background while in zsh, it's the whole {...} command group that's put in background (now in a child process) and you'd only see done after you resume it and (...) eventually terminates.

Note that if you do:

echo 'Go!' 1<> p

Where stdout is opened in read+write mode, open() doesn't block and the pipe is instantiated straight away if not already. echo would return straight away unless the pipe is full (on Linux, 64KiB (by default) have already been written to it and not read).

That open(O_RDWR) will unblock both the open(O_RDONLY) or open(O_WRONLY) that other processes may be doing.

Note that, when echo returns, if there's no other process with a fd opened on the pipe, it will be destroyed, so what echo wrote will be lost, and the next open() on the fifo will instantiate a new, separate pipe.

You can keep a fd opened on the pipe to keep it live while avoiding blocking by doing:

exec 3<> p
echo 'Go!' > p
# the pipe will remain live until at least:
exec 3>&- # to close that fd to it

That open(O_RDWR) will unblock both the open(O_RDONLY) or open(O_WRONLY) that other processes may be doing.

4
  • Ah, and so Ctrl-Z gets printed because it is handled by the terminal driver whereas Ctrl-D does not get printed because it is passed over by the terminal driver to the then blocked bash process, is that correct?
    – The Quark
    Commented yesterday
  • And indeed, I can now see with ps-eo pid,tty,command,wchan from another terminal that the bash process received a pipe_wait signal. Interesting... I did not expect the blocking to occur already at the opening of the FIFO file -- that is, even before attempting a write.
    – The Quark
    Commented yesterday
  • 1
    ^D (for eof) like ^? (erase), ^W (werase), is handled by the tty line discipline line editor. Others not involved in editing get rendered as ^X provided echoctl is on. Commented yesterday
  • 1
    A write wouldn't block until the pipe is full, but the pipe is not intentiated until the fifo file has been opened in both the read and write direction. Commented yesterday

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.