Skip to content

Commit aa31cb8

Browse files
committed
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
2 parents 72869e7 + 716c1f6 commit aa31cb8

File tree

7 files changed

+123
-27
lines changed

7 files changed

+123
-27
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,7 @@ LIB_OBJS += combine-diff.o
910910
LIB_OBJS += commit-graph.o
911911
LIB_OBJS += commit-reach.o
912912
LIB_OBJS += commit.o
913+
LIB_OBJS += compat/nonblock.o
913914
LIB_OBJS += compat/obstack.o
914915
LIB_OBJS += compat/terminal.o
915916
LIB_OBJS += compat/zlib-uncompress2.o

compat/nonblock.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include "git-compat-util.h"
2+
#include "nonblock.h"
3+
4+
#ifdef O_NONBLOCK
5+
6+
int enable_pipe_nonblock(int fd)
7+
{
8+
int flags = fcntl(fd, F_GETFL);
9+
if (flags < 0)
10+
return -1;
11+
flags |= O_NONBLOCK;
12+
return fcntl(fd, F_SETFL, flags);
13+
}
14+
15+
#elif defined(GIT_WINDOWS_NATIVE)
16+
17+
#include "win32.h"
18+
19+
int enable_pipe_nonblock(int fd)
20+
{
21+
HANDLE h = (HANDLE)_get_osfhandle(fd);
22+
DWORD mode;
23+
DWORD type = GetFileType(h);
24+
if (type == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR) {
25+
errno = EBADF;
26+
return -1;
27+
}
28+
if (type != FILE_TYPE_PIPE)
29+
BUG("unsupported file type: %lu", type);
30+
if (!GetNamedPipeHandleState(h, &mode, NULL, NULL, NULL, NULL, 0)) {
31+
errno = err_win_to_posix(GetLastError());
32+
return -1;
33+
}
34+
mode |= PIPE_NOWAIT;
35+
if (!SetNamedPipeHandleState(h, &mode, NULL, NULL)) {
36+
errno = err_win_to_posix(GetLastError());
37+
return -1;
38+
}
39+
return 0;
40+
}
41+
42+
#else
43+
44+
int enable_pipe_nonblock(int fd)
45+
{
46+
errno = ENOSYS;
47+
return -1;
48+
}
49+
50+
#endif

compat/nonblock.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#ifndef COMPAT_NONBLOCK_H
2+
#define COMPAT_NONBLOCK_H
3+
4+
/*
5+
* Enable non-blocking I/O for the pipe specified by the passed-in descriptor.
6+
*/
7+
int enable_pipe_nonblock(int fd);
8+
9+
#endif

git-compat-util.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,28 @@ static inline unsigned long cast_size_t_to_ulong(size_t a)
998998
return (unsigned long)a;
999999
}
10001000

1001+
/*
1002+
* Limit size of IO chunks, because huge chunks only cause pain. OS X
1003+
* 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
1004+
* the absence of bugs, large chunks can result in bad latencies when
1005+
* you decide to kill the process.
1006+
*
1007+
* We pick 8 MiB as our default, but if the platform defines SSIZE_MAX
1008+
* that is smaller than that, clip it to SSIZE_MAX, as a call to
1009+
* read(2) or write(2) larger than that is allowed to fail. As the last
1010+
* resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value"
1011+
* to override this, if the definition of SSIZE_MAX given by the platform
1012+
* is broken.
1013+
*/
1014+
#ifndef MAX_IO_SIZE
1015+
# define MAX_IO_SIZE_DEFAULT (8*1024*1024)
1016+
# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT)
1017+
# define MAX_IO_SIZE SSIZE_MAX
1018+
# else
1019+
# define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT
1020+
# endif
1021+
#endif
1022+
10011023
#ifdef HAVE_ALLOCA_H
10021024
# include <alloca.h>
10031025
# define xalloca(size) (alloca(size))

run-command.c

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "config.h"
1111
#include "packfile.h"
1212
#include "hook.h"
13+
#include "compat/nonblock.h"
1314

1415
void child_process_init(struct child_process *child)
1516
{
@@ -1364,12 +1365,25 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)
13641365
continue;
13651366

13661367
if (io->type == POLLOUT) {
1367-
ssize_t len = xwrite(io->fd,
1368-
io->u.out.buf, io->u.out.len);
1368+
ssize_t len;
1369+
1370+
/*
1371+
* Don't use xwrite() here. It loops forever on EAGAIN,
1372+
* and we're in our own poll() loop here.
1373+
*
1374+
* Note that we lose xwrite()'s handling of MAX_IO_SIZE
1375+
* and EINTR, so we have to implement those ourselves.
1376+
*/
1377+
len = write(io->fd, io->u.out.buf,
1378+
io->u.out.len <= MAX_IO_SIZE ?
1379+
io->u.out.len : MAX_IO_SIZE);
13691380
if (len < 0) {
1370-
io->error = errno;
1371-
close(io->fd);
1372-
io->fd = -1;
1381+
if (errno != EINTR && errno != EAGAIN &&
1382+
errno != ENOSPC) {
1383+
io->error = errno;
1384+
close(io->fd);
1385+
io->fd = -1;
1386+
}
13731387
} else {
13741388
io->u.out.buf += len;
13751389
io->u.out.len -= len;
@@ -1438,6 +1452,15 @@ int pipe_command(struct child_process *cmd,
14381452
return -1;
14391453

14401454
if (in) {
1455+
if (enable_pipe_nonblock(cmd->in) < 0) {
1456+
error_errno("unable to make pipe non-blocking");
1457+
close(cmd->in);
1458+
if (out)
1459+
close(cmd->out);
1460+
if (err)
1461+
close(cmd->err);
1462+
return -1;
1463+
}
14411464
io[nr].fd = cmd->in;
14421465
io[nr].type = POLLOUT;
14431466
io[nr].u.out.buf = in;

t/t3701-add-interactive.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,19 @@ test_expect_success 'detect bogus diffFilter output' '
766766
force_color test_must_fail git add -p <y
767767
'
768768

769+
test_expect_success 'handle very large filtered diff' '
770+
git reset --hard &&
771+
# The specific number here is not important, but it must
772+
# be large enough that the output of "git diff --color"
773+
# fills up the pipe buffer. 10,000 results in ~200k of
774+
# colored output.
775+
test_seq 10000 >test &&
776+
test_config interactive.diffFilter cat &&
777+
printf y >y &&
778+
force_color git add -p >output 2>&1 <y &&
779+
git diff-files --exit-code -- test
780+
'
781+
769782
test_expect_success 'diff.algorithm is passed to `git diff-files`' '
770783
git reset --hard &&
771784

wrapper.c

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -161,28 +161,6 @@ void xsetenv(const char *name, const char *value, int overwrite)
161161
die_errno(_("could not setenv '%s'"), name ? name : "(null)");
162162
}
163163

164-
/*
165-
* Limit size of IO chunks, because huge chunks only cause pain. OS X
166-
* 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in
167-
* the absence of bugs, large chunks can result in bad latencies when
168-
* you decide to kill the process.
169-
*
170-
* We pick 8 MiB as our default, but if the platform defines SSIZE_MAX
171-
* that is smaller than that, clip it to SSIZE_MAX, as a call to
172-
* read(2) or write(2) larger than that is allowed to fail. As the last
173-
* resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value"
174-
* to override this, if the definition of SSIZE_MAX given by the platform
175-
* is broken.
176-
*/
177-
#ifndef MAX_IO_SIZE
178-
# define MAX_IO_SIZE_DEFAULT (8*1024*1024)
179-
# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT)
180-
# define MAX_IO_SIZE SSIZE_MAX
181-
# else
182-
# define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT
183-
# endif
184-
#endif
185-
186164
/**
187165
* xopen() is the same as open(), but it die()s if the open() fails.
188166
*/

0 commit comments

Comments
 (0)