summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff King <peff@github.com>2011-04-06 17:33:33 -0400
committerJunio C Hamano <gitster@pobox.com>2011-04-06 14:38:47 -0700
commitb9612197798dbfc622c766e83b1fe4c20bffae5c (patch)
tree60632d2fcac76911b859bfd96c215c14465cef4c
parentd424a47e3400ef597a6f4f3c56ee67290f7c6052 (diff)
downloadgit-b9612197798dbfc622c766e83b1fe4c20bffae5c.tar.gz
upload-pack: start pack-objects before async rev-list
In a pthread-enabled version of upload-pack, there's a race condition that can cause a deadlock on the fflush(NULL) we call from run-command. What happens is this: 1. Upload-pack is informed we are doing a shallow clone. 2. We call start_async() to spawn a thread that will generate rev-list results to feed to pack-objects. It gets a file descriptor to a pipe which will eventually hook to pack-objects. 3. The rev-list thread uses fdopen to create a new output stream around the fd we gave it, called pack_pipe. 4. The thread writes results to pack_pipe. Outside of our control, libc is doing locking on the stream. We keep writing until the OS pipe buffer is full, and then we block in write(), still holding the lock. 5. The main thread now uses start_command to spawn pack-objects. Before forking, it calls fflush(NULL) to flush every stdio output buffer. It blocks trying to get the lock on pack_pipe. And we have a deadlock. The thread will block until somebody starts reading from the pipe. But nobody will read from the pipe until we finish flushing to the pipe. To fix this, we swap the start order: we start the pack-objects reader first, and then the rev-list writer after. Thus the problematic fflush(NULL) happens before we even open the new file descriptor (and even if it didn't, flushing should no longer block, as the reader at the end of the pipe is now active). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Notes
Notes: t5500.12 "fetch same depth in shallow repo" reproducibly hangs[1] on the HURD without this patch and passes with it. I had just assumed it was some weird hurd thing. Thanks for figuring it out. Tested-by: Jonathan Nieder <jrnieder@gmail.com>
-rw-r--r--upload-pack.c23
1 files changed, 11 insertions, 12 deletions
diff --git a/upload-pack.c b/upload-pack.c
index b40a43f27d..45a60e60fc 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -156,15 +156,8 @@ static void create_pack_file(void)
const char *argv[10];
int arg = 0;
- if (shallow_nr) {
- memset(&rev_list, 0, sizeof(rev_list));
- rev_list.proc = do_rev_list;
- rev_list.out = -1;
- if (start_async(&rev_list))
- die("git upload-pack: unable to fork git-rev-list");
- argv[arg++] = "pack-objects";
- } else {
- argv[arg++] = "pack-objects";
+ argv[arg++] = "pack-objects";
+ if (!shallow_nr) {
argv[arg++] = "--revs";
if (create_full_pack)
argv[arg++] = "--all";
@@ -182,7 +175,7 @@ static void create_pack_file(void)
argv[arg++] = NULL;
memset(&pack_objects, 0, sizeof(pack_objects));
- pack_objects.in = shallow_nr ? rev_list.out : -1;
+ pack_objects.in = -1;
pack_objects.out = -1;
pack_objects.err = -1;
pack_objects.git_cmd = 1;
@@ -191,8 +184,14 @@ static void create_pack_file(void)
if (start_command(&pack_objects))
die("git upload-pack: unable to fork git-pack-objects");
- /* pass on revisions we (don't) want */
- if (!shallow_nr) {
+ if (shallow_nr) {
+ memset(&rev_list, 0, sizeof(rev_list));
+ rev_list.proc = do_rev_list;
+ rev_list.out = pack_objects.in;
+ if (start_async(&rev_list))
+ die("git upload-pack: unable to fork git-rev-list");
+ }
+ else {
FILE *pipe_fd = xfdopen(pack_objects.in, "w");
if (!create_full_pack) {
int i;