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-graph.o
|
||||||
LIB_OBJS += commit-reach.o
|
LIB_OBJS += commit-reach.o
|
||||||
LIB_OBJS += commit.o
|
LIB_OBJS += commit.o
|
||||||
|
LIB_OBJS += compat/nonblock.o
|
||||||
LIB_OBJS += compat/obstack.o
|
LIB_OBJS += compat/obstack.o
|
||||||
LIB_OBJS += compat/terminal.o
|
LIB_OBJS += compat/terminal.o
|
||||||
LIB_OBJS += compat/zlib-uncompress2.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;
|
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
|
#ifdef HAVE_ALLOCA_H
|
||||||
# include <alloca.h>
|
# include <alloca.h>
|
||||||
# define xalloca(size) (alloca(size))
|
# define xalloca(size) (alloca(size))
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "packfile.h"
|
#include "packfile.h"
|
||||||
#include "hook.h"
|
#include "hook.h"
|
||||||
|
#include "compat/nonblock.h"
|
||||||
|
|
||||||
void child_process_init(struct child_process *child)
|
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;
|
continue;
|
||||||
|
|
||||||
if (io->type == POLLOUT) {
|
if (io->type == POLLOUT) {
|
||||||
ssize_t len = xwrite(io->fd,
|
ssize_t len;
|
||||||
io->u.out.buf, io->u.out.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) {
|
if (len < 0) {
|
||||||
io->error = errno;
|
if (errno != EINTR && errno != EAGAIN &&
|
||||||
close(io->fd);
|
errno != ENOSPC) {
|
||||||
io->fd = -1;
|
io->error = errno;
|
||||||
|
close(io->fd);
|
||||||
|
io->fd = -1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
io->u.out.buf += len;
|
io->u.out.buf += len;
|
||||||
io->u.out.len -= len;
|
io->u.out.len -= len;
|
||||||
@ -1438,6 +1452,15 @@ int pipe_command(struct child_process *cmd,
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (in) {
|
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].fd = cmd->in;
|
||||||
io[nr].type = POLLOUT;
|
io[nr].type = POLLOUT;
|
||||||
io[nr].u.out.buf = in;
|
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
|
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`' '
|
test_expect_success 'diff.algorithm is passed to `git diff-files`' '
|
||||||
git reset --hard &&
|
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)");
|
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.
|
* xopen() is the same as open(), but it die()s if the open() fails.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user