Re: hooks that do not consume stdin sometimes crash git with SIGPIPE

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, Dec 05, 2011 at 03:29:30PM -0400, Joey Hess wrote:

> We had a weird problem where, after moving to a new, faster server,
> "git push" would sometimes fail like this:
> 
> Unpacking objects: 100% (3/3), done.
> fatal: The remote end hung up unexpectedly
> fatal: The remote end hung up unexpectedly
> 
> Turns out that git-receive-pack was dying due to an uncaught SIGPIPE.
> The SIGPIPE occurred when it tried to write to the pre-receive hook's
> stdin. The pre-receive hook, in this case, was able to do all the checks
> it needed to do[1] without the input, and so did exit(0) without
> consuming it.

Ugh. Yeah, receive-pack should probably just be ignoring SIGPIPE
entirely. It checks the return from write() properly where it matters,
so SIGPIPE is just an annoyance.

I would go so far as to say that git should be ignoring SIGPIPE 99% of
the time. We have crap like this sprinkled throughout the code base:

  $ git grep -C1 SIGPIPE
  builtin/tag.c-  /* When the username signingkey is bad, program could be terminated
  builtin/tag.c:   * because gpg exits without reading and then write gets SIGPIPE. */
  builtin/tag.c:  signal(SIGPIPE, SIG_IGN);
  [...]
  upload-pack.c-   * If rev-list --stdin encounters an unknown commit, it
  upload-pack.c:   * terminates, which will cause SIGPIPE in the write loop
  upload-pack.c-   */
  upload-pack.c:  sigchain_push(SIGPIPE, SIG_IGN);

but I find it highly unlikely that they are covering all of the cases.
You found one already, and these things can quite often be
race-condition heisenbugs.

The one place where SIGPIPE _is_ useful is for things like "git log"
which are just dumping to a pager over stdout. When the pager dies, we
can stop bothering to produce output.

It would be really nice if we could write a sigpipe handler that knew
which fd caused the the signal, and then we could do something like:

  void sigpipe_handler(int sig)
  {
          /* If we're writing to a pager over stdout, then there is
           * little use in writing more; nobody is interested in our
           * output. */
          if (get_fd_that_caused_sigpipe() == 1 && pager_in_use)
                  exit(1);

          /* Otherwise, ignore it, as it's a write to some auxiliary
           * process and we will be careful about checking the return
           * code from write(). */
  }

But I don't think such a function exists. We could just check
pager_in_use, which would cover most cases (e.g., programs like
receive-pack don't use a pager). But it would fail in the case of
something like "git log" using an auxiliary process that closes the pipe
early. Maybe that would be good enough, though. I dunno.

Another option is to just ignore SIGPIPE entirely, and convince programs
like log to actually bother checking the result of write(). It would be
a slight pain to check every printf() call we make in log-tree.c, but we
could do one of:

  1. Make a set of stdio wrapper scripts that exit gracefully on EPIPE.
     Using the wrapper scripts everywhere is a slight pain, but would
     work pretty well, and adapts easily to other places like printing
     lists of refs, etc.

  2. Don't check every printf. After printing each commit, check
     ferror(stdout) and exit as appropriate. This is a very small amount
     of code, but you'd need to do it in several places (i.e., anywhere
     that produces a lot of output).

> I think git should ignore SIGPIPE when writing to hooks. Otherwise,
> hooks may have to go out of their way to consume all input, and as I've
> seen, the races when they fail to do this can lurk undiscovered.

Yeah, certainly. The question to me is whether we should just stick a
SIG_IGN in the beginning of receive-pack, or whether we should try to
deal with this problem everywhere.

For example, I suspect the same problem exists in the credential helpers
I wrote recently. Generally they will read all of their input, but you
could do something like:

  [credential "https://github.com";]
          username = peff
          helper = "!
                  f() {
                          # if we call this helper, we know we want the
                          # github password,  or else this helper config
                          # would never have been triggered.  so we
                          # don't even have to bother reading our stdin.

                          # We only handle getting the password.
                          test "$1" = "get" || return

                          # Presumably gpg-agent will ask for and cache
                          # your gpg password.
                          p=`gpg -qd --no-tty <~/.github-password.gpg`

                          echo "password=$p"
                  }; f"

This can racily fail if our write happens after the helper has already
finished (unlikely, but possible).

-Peff
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]