Merge branch 'jk/pipe-command-nonblock' into maint
Fix deadlocks between main Git process and subprocess spawned via the pipe_command() API, that can kill "git add -p" that was reimplemented in C recently. * jk/pipe-command-nonblock: pipe_command(): mark stdin descriptor as non-blocking pipe_command(): handle ENOSPC when writing to a pipe pipe_command(): avoid xwrite() for writing to pipe git-compat-util: make MAX_IO_SIZE define globally available nonblock: support Windows compat: add function to enable nonblocking pipes
This commit is contained in:
commit
aa31cb8974
1
Makefile
1
Makefile
@ -910,6 +910,7 @@ LIB_OBJS += combine-diff.o
|
||||
LIB_OBJS += commit-graph.o
|
||||
LIB_OBJS += commit-reach.o
|
||||
LIB_OBJS += commit.o
|
||||
LIB_OBJS += compat/nonblock.o
|
||||
LIB_OBJS += compat/obstack.o
|
||||
LIB_OBJS += compat/terminal.o
|
||||
LIB_OBJS += compat/zlib-uncompress2.o
|
||||
|
50
compat/nonblock.c
Normal file
50
compat/nonblock.c
Normal file
@ -0,0 +1,50 @@
|
||||
#include "git-compat-util.h"
|
||||
#include "nonblock.h"
|
||||
|
||||
#ifdef O_NONBLOCK
|
||||
|
||||
int enable_pipe_nonblock(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFL);
|
||||
if (flags < 0)
|
||||
return -1;
|
||||
flags |= O_NONBLOCK;
|
||||
return fcntl(fd, F_SETFL, flags);
|
||||
}
|
||||
|
||||
#elif defined(GIT_WINDOWS_NATIVE)
|
||||
|
||||
#include "win32.h"
|
||||
|
||||
int enable_pipe_nonblock(int fd)
|
||||
{
|
||||
HANDLE h = (HANDLE)_get_osfhandle(fd);
|
||||
DWORD mode;
|
||||
DWORD type = GetFileType(h);
|
||||
if (type == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
if (type != FILE_TYPE_PIPE)
|
||||
BUG("unsupported file type: %lu", type);
|
||||
if (!GetNamedPipeHandleState(h, &mode, NULL, NULL, NULL, NULL, 0)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
mode |= PIPE_NOWAIT;
|
||||
if (!SetNamedPipeHandleState(h, &mode, NULL, NULL)) {
|
||||
errno = err_win_to_posix(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int enable_pipe_nonblock(int fd)
|
||||
{
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
9
compat/nonblock.h
Normal file
9
compat/nonblock.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef COMPAT_NONBLOCK_H
|
||||
#define COMPAT_NONBLOCK_H
|
||||
|
||||
/*
|
||||
* Enable non-blocking I/O for the pipe specified by the passed-in descriptor.
|
||||
*/
|
||||
int enable_pipe_nonblock(int fd);
|
||||
|
||||
#endif
|
@ -998,6 +998,28 @@ static inline unsigned long cast_size_t_to_ulong(size_t a)
|
||||
return (unsigned long)a;
|
||||
}
|
||||
|
||||
/*
|
||||
* Limit size of IO chunks, because huge chunks only cause pain. OS X
|
||||
* 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
|
||||
* the absence of bugs, large chunks can result in bad latencies when
|
||||
* you decide to kill the process.
|
||||
*
|
||||
* We pick 8 MiB as our default, but if the platform defines SSIZE_MAX
|
||||
* that is smaller than that, clip it to SSIZE_MAX, as a call to
|
||||
* read(2) or write(2) larger than that is allowed to fail. As the last
|
||||
* resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value"
|
||||
* to override this, if the definition of SSIZE_MAX given by the platform
|
||||
* is broken.
|
||||
*/
|
||||
#ifndef MAX_IO_SIZE
|
||||
# define MAX_IO_SIZE_DEFAULT (8*1024*1024)
|
||||
# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT)
|
||||
# define MAX_IO_SIZE SSIZE_MAX
|
||||
# else
|
||||
# define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# define xalloca(size) (alloca(size))
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "config.h"
|
||||
#include "packfile.h"
|
||||
#include "hook.h"
|
||||
#include "compat/nonblock.h"
|
||||
|
||||
void child_process_init(struct child_process *child)
|
||||
{
|
||||
@ -1364,12 +1365,25 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)
|
||||
continue;
|
||||
|
||||
if (io->type == POLLOUT) {
|
||||
ssize_t len = xwrite(io->fd,
|
||||
io->u.out.buf, io->u.out.len);
|
||||
ssize_t len;
|
||||
|
||||
/*
|
||||
* Don't use xwrite() here. It loops forever on EAGAIN,
|
||||
* and we're in our own poll() loop here.
|
||||
*
|
||||
* Note that we lose xwrite()'s handling of MAX_IO_SIZE
|
||||
* and EINTR, so we have to implement those ourselves.
|
||||
*/
|
||||
len = write(io->fd, io->u.out.buf,
|
||||
io->u.out.len <= MAX_IO_SIZE ?
|
||||
io->u.out.len : MAX_IO_SIZE);
|
||||
if (len < 0) {
|
||||
io->error = errno;
|
||||
close(io->fd);
|
||||
io->fd = -1;
|
||||
if (errno != EINTR && errno != EAGAIN &&
|
||||
errno != ENOSPC) {
|
||||
io->error = errno;
|
||||
close(io->fd);
|
||||
io->fd = -1;
|
||||
}
|
||||
} else {
|
||||
io->u.out.buf += len;
|
||||
io->u.out.len -= len;
|
||||
@ -1438,6 +1452,15 @@ int pipe_command(struct child_process *cmd,
|
||||
return -1;
|
||||
|
||||
if (in) {
|
||||
if (enable_pipe_nonblock(cmd->in) < 0) {
|
||||
error_errno("unable to make pipe non-blocking");
|
||||
close(cmd->in);
|
||||
if (out)
|
||||
close(cmd->out);
|
||||
if (err)
|
||||
close(cmd->err);
|
||||
return -1;
|
||||
}
|
||||
io[nr].fd = cmd->in;
|
||||
io[nr].type = POLLOUT;
|
||||
io[nr].u.out.buf = in;
|
||||
|
@ -766,6 +766,19 @@ test_expect_success 'detect bogus diffFilter output' '
|
||||
force_color test_must_fail git add -p <y
|
||||
'
|
||||
|
||||
test_expect_success 'handle very large filtered diff' '
|
||||
git reset --hard &&
|
||||
# The specific number here is not important, but it must
|
||||
# be large enough that the output of "git diff --color"
|
||||
# fills up the pipe buffer. 10,000 results in ~200k of
|
||||
# colored output.
|
||||
test_seq 10000 >test &&
|
||||
test_config interactive.diffFilter cat &&
|
||||
printf y >y &&
|
||||
force_color git add -p >output 2>&1 <y &&
|
||||
git diff-files --exit-code -- test
|
||||
'
|
||||
|
||||
test_expect_success 'diff.algorithm is passed to `git diff-files`' '
|
||||
git reset --hard &&
|
||||
|
||||
|
22
wrapper.c
22
wrapper.c
@ -161,28 +161,6 @@ void xsetenv(const char *name, const char *value, int overwrite)
|
||||
die_errno(_("could not setenv '%s'"), name ? name : "(null)");
|
||||
}
|
||||
|
||||
/*
|
||||
* Limit size of IO chunks, because huge chunks only cause pain. OS X
|
||||
* 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
|
||||
* the absence of bugs, large chunks can result in bad latencies when
|
||||
* you decide to kill the process.
|
||||
*
|
||||
* We pick 8 MiB as our default, but if the platform defines SSIZE_MAX
|
||||
* that is smaller than that, clip it to SSIZE_MAX, as a call to
|
||||
* read(2) or write(2) larger than that is allowed to fail. As the last
|
||||
* resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value"
|
||||
* to override this, if the definition of SSIZE_MAX given by the platform
|
||||
* is broken.
|
||||
*/
|
||||
#ifndef MAX_IO_SIZE
|
||||
# define MAX_IO_SIZE_DEFAULT (8*1024*1024)
|
||||
# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT)
|
||||
# define MAX_IO_SIZE SSIZE_MAX
|
||||
# else
|
||||
# define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* xopen() is the same as open(), but it die()s if the open() fails.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user