2

If you create a shell script in your PATH called git-mydiff that contains:

#!/bin/bash
exec git diff

and call git mydiff in a repository with a large amount of changes, when you quit the pager, it will output:

error: git-mydiff died of signal 13

However, if you execute path/to/git-mydiff directly, there will be no error when you exit the pager.

Obviously one solution is to not use exec, but why is this a problem? Why is it only an issue when calling the script through the git proxy command?

I'm using: git version 2.5.4 (Apple Git-61)

redtree
  • 295
  • 1
  • 2
  • 12
  • One possibility is to ignore SIGPIPE like in http://stackoverflow.com/a/22464942/5781248 – J.J. Hakala Jan 31 '16 at 17:56
  • @J.J.Hakala I tried that, but it didn't seem to work. Because `exec` totally replaces the currently running program with a new one, I don't think signal traps are inherited. This is discussed here: http://stackoverflow.com/questions/24111981/how-can-i-achieve-bash-exit-trap-when-exec-ing-another-binary – redtree Jan 31 '16 at 18:02

1 Answers1

5

Your program, in this case mydiff, and the pager (less, or whatever you have selected as core.pager) are connected through a pipe. The OS has some limit on the amount of data that can be written into a pipe before the reader must clean some out, and the pager doesn't read the entire pipe before pausing, so at some amount of output, the pipe has filled up and your program is blocked in its write system call.

If the read end of the pipe goes away (by having the pager exit), two things happen at this point: the OS delivers a SIGPIPE signal to your program, and the OS has the write system call fail with an EPIPE error. Normally the first—the signal—kills your program before the second even occurs, but if your program were to catch or ignore SIGPIPE, the second would occur.

Here's an example in a job-control shell of SIGPIPE killing a process:

> cat book.pdf | : &
>
[1]    Broken pipe                   cat book.pdf |
       Done                          :

(Incidentally : here is the built in colon command, which is a no-op; it's a leftover from, I think, the Mashey shell that had goto as an external program.) Running this as a regular foreground process, the sequence is silent:

> cat book.pdf | :
>

This is because the shell does not complain about the died-of-SIGPIPE process, since "died of SIGPIPE" is quite normal.

For whatever reason, the git front end is noisier about this died-of-SIGPIPE case. If you don't use exec, it is the shell that sees the died-of-SIGPIPE. The shell absorbs that quietly and exits cleanly and git does not complain. If you do use exec, the shell is replaced with your program, and the git front end command sees the died-of-SIGPIPE status and complains.

One obvious cure is to leave the shell around. Another approach is to get the shell to ignore (not catch) SIGPIPE, then do the exec:

trap "" PIPE

As you noted in a comment-reply, catching SIGPIPE is no good: since exec replaces the current occupant of the address space, the OS resets all caught signals to their default disposition (which for SIGPIPE is "die of signal"). However, ignored signals remain ignored.

Depending on your program, this may be as bad or worse. For instance, when cat dies of SIGPIPE the shell is silent, but when cat sees write fail with EPIPE it complains:

$ cat book.pdf | :
$ trap "" PIPE
$ cat book.pdf | :
cat: stdout: Broken pipe

(this is on FreeBSD; different OSes vary slightly, depending on how careful and clever their utilities are).

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you for the in-depth explanation! If I add `trap "" PIPE` to the script before `exec git diff`, I still see "died of signal 13" when `less` exits. Shouldn't it now be failing due to `EPIPE` rather than `SIGPIPE`? – redtree Jan 31 '16 at 19:57
  • 1
    It should, yes (unless it's doing something weird like explicitly resetting the signal to default). Might be interesting to dump out the syscalls being made (trace, truss, ktrace, whatever the OS provides...). – torek Jan 31 '16 at 19:58