Re: [PATCH 12/12] receive-pack: send keepalives during quiet periods
On Tue, Jul 19, 2016 at 09:05:14AM -0700, Stefan Beller wrote: > > I know that writing the function myself and then critiquing is the very > > definition of a strawman. :) But it's the best I could think of. Maybe > > you had something more clever in mind? > > Actually no, I had not. I was hoping you could come up with a clever thing. > My original point was the perceived added complexity to a simple > seemingly simple function (copy_to_sideband in your original patch), > so my gut reaction was to shove the complexity away into a helper function. > But no matter how it is done, there is always this one function that looks > complex for this problem. So I think your original approach is fine then? Yeah, I agree the original was nicer, but IMHO none of the "like this" alternatives I showed in this thread are real improvements. So I'm inclined to go with what was originally posted (though I'm open to more input or suggestions). The only avenue I didn't look at is actually ditching the async muxer completely for the index-pack call, and replacing it with the main thread doing a poll() on stdout/stderr from index-pack. That's probably going to end up as _more_ code, but it potentially makes the "report magic NUL" a little less gross (we could signal with something more sane on stdout). I can explore that if people want me to. If not, then the only change to the original series is adding "static" to the rev-list progress variables that Junio noticed. A replacement for patch 2 is below in case that makes things easier. -- >8 -- Subject: [PATCH] rev-list: add optional progress reporting It's easy to ask rev-list to do a traversal that may takes many seconds (e.g., by calling "--objects --all"). In theory you can monitor its progress by the output you get to stdout, but this isn't always easy. Some operations, like "--count", don't make any output until the end. And some callers, like check_everything_connected(), are using it just for the error-checking of the traversal, and throw away stdout entirely. This patch adds a "--progress" option which can be used to give some eye-candy for a user waiting for a long traversal. This is just a rev-list option and not a regular traversal option, because it needs cooperation from the callbacks in builtin/rev-list.c to do the actual count. Signed-off-by: Jeff King--- Documentation/rev-list-options.txt | 4 builtin/rev-list.c | 17 + 2 files changed, 21 insertions(+) diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index c5bd218..f39cb6d 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -274,6 +274,10 @@ ifdef::git-rev-list[] Try to speed up the traversal using the pack bitmap index (if one is available). Note that when traversing with `--objects`, trees and blobs will not have their associated path printed. + +--progress=:: + Show progress reports on stderr as objects are considered. The + `` text will be printed with each progress update. endif::git-rev-list[] -- diff --git a/builtin/rev-list.c b/builtin/rev-list.c index b82bcc3..0ba82b1 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -9,6 +9,7 @@ #include "log-tree.h" #include "graph.h" #include "bisect.h" +#include "progress.h" static const char rev_list_usage[] = "git rev-list [OPTION] ... [ -- paths... ]\n" @@ -49,12 +50,17 @@ static const char rev_list_usage[] = "--bisect-all" ; +static struct progress *progress; +static unsigned progress_counter; + static void finish_commit(struct commit *commit, void *data); static void show_commit(struct commit *commit, void *data) { struct rev_list_info *info = data; struct rev_info *revs = info->revs; + display_progress(progress, ++progress_counter); + if (info->flags & REV_LIST_QUIET) { finish_commit(commit, data); return; @@ -190,6 +196,7 @@ static void show_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; finish_object(obj, name, cb_data); + display_progress(progress, ++progress_counter); if (info->flags & REV_LIST_QUIET) return; show_object_with_name(stdout, obj, name); @@ -276,6 +283,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int bisect_show_vars = 0; int bisect_find_all = 0; int use_bitmap_index = 0; + const char *show_progress = NULL; git_config(git_default_config, NULL); init_revisions(, prefix); @@ -325,6 +333,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) test_bitmap_walk(); return 0; } + if (skip_prefix(arg, "--progress=", )) { + show_progress = arg; + continue; +
Re: [PATCH 12/12] receive-pack: send keepalives during quiet periods
On Tue, Jul 19, 2016 at 3:07 AM, Jeff Kingwrote: > On Mon, Jul 18, 2016 at 10:28:25PM -0700, Stefan Beller wrote: > >> On Sat, Jul 16, 2016 at 12:56 AM, Jeff King wrote: >> >> > + if (use_keepalive == KEEPALIVE_AFTER_NUL && >> >> > !keepalive_active) { >> >> > + const char *p = memchr(data, '\0', sz); >> >> > + if (p) { >> >> > + /* >> >> > +* The NUL tells us to start sending >> >> > keepalives. Make >> >> > +* sure we send any other data we read >> >> > along >> >> > +* with it. >> >> > +*/ >> >> > + keepalive_active = 1; >> >> > + send_sideband(1, 2, data, p - data, >> >> > use_sideband); >> >> > + send_sideband(1, 2, p + 1, sz - (p - >> >> > data + 1), use_sideband); >> >> > + continue; >> >> >> >> Oh, I see why the turn_on_keepalive_on_NUL doesn't work as well as I >> >> thought. >> >> I wonder if we can use a better read function, that would stop reading at >> >> a NUL, >> >> and return early instead? >> > >> > How would you do that, if not by read()ing a byte at a time (which is >> > inefficient)? Otherwise you have to deal with the leftovers (after the >> > NUL) in your buffer. It's one of the reasons I went with a single-byte >> > signal, because otherwise it gets rather complicated to do robustly. >> >> I do not question the concept of a single NUL byte, but rather the >> implementation, i.e. if we had an xread_until_nul you would not need >> to have a double send_sideband here? > > What would xread_until_nul() look like? > > The only reasonable implementation I can think of is: > > ssize_t xread_until_nul(int fd, char *out, size_t len) > { > ssize_t total = 0; > while (total < len) { > ssize_t ret = xread(fd, out + total, 1); > if (ret < 0) { > /* Oops, anything in out[0..total] is lost, but > * we have no way of signaling both a partial > * read and an error at the end; fixable by > * changing the interface, but doesn't really > * matter in practice for this application. */ > return -1; > } > if (ret == 0) > break; /* EOF, stop reading */ > if (out[total] == '\0') > break; /* got our NUL, stop reading */ > total++; > } > return total; > } > > There are two problems with this function: > > 1. Until we see the NUL, we're making an excessive number of read() > syscalls looking for it. You could make larger reads, but then what > do you do with the residual bytes (i.e., the ones after the NUL in > the buffer you read)? You'd have to somehow save it to return at > the next xread_until_nul(). Which implie some kind of internal > buffering. > > The reason there are two send_sidebands is to cover the case where > we have some real data, then the signal byte, then some more data. > So instead of buffering, we just handle the data immediately. > > 2. How does it know when to return? > > We want to send the data as soon as it is available, in as large a > chunk as possible. Using a single xread() as we do now, we get > whatever the OS has for us, up to our buffer size. > > But after each 1-byte read above, we would not want to return; > there might be more data. So it keeps reading until NUL or we fill > our buffer. But that may mean the xread() blocks, even though we > have data that _could_ be shipped over the sideband. > > To fix that, you'd have to poll() for each xread(), and return when > it says nothing's ready. > > I know that writing the function myself and then critiquing is the very > definition of a strawman. :) But it's the best I could think of. Maybe > you had something more clever in mind? Actually no, I had not. I was hoping you could come up with a clever thing. My original point was the perceived added complexity to a simple seemingly simple function (copy_to_sideband in your original patch), so my gut reaction was to shove the complexity away into a helper function. But no matter how it is done, there is always this one function that looks complex for this problem. So I think your original approach is fine then? Thanks, Stefan > > -Peff -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH 12/12] receive-pack: send keepalives during quiet periods
On Mon, Jul 18, 2016 at 10:28:25PM -0700, Stefan Beller wrote: > On Sat, Jul 16, 2016 at 12:56 AM, Jeff Kingwrote: > >> > + if (use_keepalive == KEEPALIVE_AFTER_NUL && > >> > !keepalive_active) { > >> > + const char *p = memchr(data, '\0', sz); > >> > + if (p) { > >> > + /* > >> > +* The NUL tells us to start sending > >> > keepalives. Make > >> > +* sure we send any other data we read > >> > along > >> > +* with it. > >> > +*/ > >> > + keepalive_active = 1; > >> > + send_sideband(1, 2, data, p - data, > >> > use_sideband); > >> > + send_sideband(1, 2, p + 1, sz - (p - > >> > data + 1), use_sideband); > >> > + continue; > >> > >> Oh, I see why the turn_on_keepalive_on_NUL doesn't work as well as I > >> thought. > >> I wonder if we can use a better read function, that would stop reading at > >> a NUL, > >> and return early instead? > > > > How would you do that, if not by read()ing a byte at a time (which is > > inefficient)? Otherwise you have to deal with the leftovers (after the > > NUL) in your buffer. It's one of the reasons I went with a single-byte > > signal, because otherwise it gets rather complicated to do robustly. > > I do not question the concept of a single NUL byte, but rather the > implementation, i.e. if we had an xread_until_nul you would not need > to have a double send_sideband here? What would xread_until_nul() look like? The only reasonable implementation I can think of is: ssize_t xread_until_nul(int fd, char *out, size_t len) { ssize_t total = 0; while (total < len) { ssize_t ret = xread(fd, out + total, 1); if (ret < 0) { /* Oops, anything in out[0..total] is lost, but * we have no way of signaling both a partial * read and an error at the end; fixable by * changing the interface, but doesn't really * matter in practice for this application. */ return -1; } if (ret == 0) break; /* EOF, stop reading */ if (out[total] == '\0') break; /* got our NUL, stop reading */ total++; } return total; } There are two problems with this function: 1. Until we see the NUL, we're making an excessive number of read() syscalls looking for it. You could make larger reads, but then what do you do with the residual bytes (i.e., the ones after the NUL in the buffer you read)? You'd have to somehow save it to return at the next xread_until_nul(). Which implie some kind of internal buffering. The reason there are two send_sidebands is to cover the case where we have some real data, then the signal byte, then some more data. So instead of buffering, we just handle the data immediately. 2. How does it know when to return? We want to send the data as soon as it is available, in as large a chunk as possible. Using a single xread() as we do now, we get whatever the OS has for us, up to our buffer size. But after each 1-byte read above, we would not want to return; there might be more data. So it keeps reading until NUL or we fill our buffer. But that may mean the xread() blocks, even though we have data that _could_ be shipped over the sideband. To fix that, you'd have to poll() for each xread(), and return when it says nothing's ready. I know that writing the function myself and then critiquing is the very definition of a strawman. :) But it's the best I could think of. Maybe you had something more clever in mind? -Peff -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH 12/12] receive-pack: send keepalives during quiet periods
On Sat, Jul 16, 2016 at 12:56 AM, Jeff Kingwrote: >> > + if (use_keepalive == KEEPALIVE_AFTER_NUL && >> > !keepalive_active) { >> > + const char *p = memchr(data, '\0', sz); >> > + if (p) { >> > + /* >> > +* The NUL tells us to start sending >> > keepalives. Make >> > +* sure we send any other data we read >> > along >> > +* with it. >> > +*/ >> > + keepalive_active = 1; >> > + send_sideband(1, 2, data, p - data, >> > use_sideband); >> > + send_sideband(1, 2, p + 1, sz - (p - data >> > + 1), use_sideband); >> > + continue; >> >> Oh, I see why the turn_on_keepalive_on_NUL doesn't work as well as I thought. >> I wonder if we can use a better read function, that would stop reading at a >> NUL, >> and return early instead? > > How would you do that, if not by read()ing a byte at a time (which is > inefficient)? Otherwise you have to deal with the leftovers (after the > NUL) in your buffer. It's one of the reasons I went with a single-byte > signal, because otherwise it gets rather complicated to do robustly. I do not question the concept of a single NUL byte, but rather the implementation, i.e. if we had an xread_until_nul you would not need to have a double send_sideband here? > > -Peff -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH 12/12] receive-pack: send keepalives during quiet periods
On Fri, Jul 15, 2016 at 10:24:16AM -0700, Stefan Beller wrote: > > @@ -319,10 +331,60 @@ static void rp_error(const char *err, ...) > > static int copy_to_sideband(int in, int out, void *arg) > > { > > char data[128]; > > While looking at this code, do you think it is feasible to increase the > size of data[] to 1024 ? (The largest that is possible when > side-band, but no side-band-64k is given). I don't see any reason it could not be increased. On the other hand, I find it unlikely to accomplish anything. We are relaying stderr over sideband 2 here, so assuming that typically consists of human-readable lines, they're unlikely to go over 128 (and if they do, they simply get split into two packets). I suppose if your hooks dump large ascii art to the user, we could save a few framing bytes. So I don't mind it, but it's definitely orthogonal to this series. > The method was short and concise, this adds a lot of lines. > Remembering d751dd11 (2016-07-10, hoist out handle_nonblock > function for xread and xwrite), do you think it would be reasonable to > put the whole poll handling into a dedicated function, maybe even reuse the > that function? > > if (keepalive_active) { > if (wrapper_around_poll(_in) < 0) // handles EINTR internally > break; > if (!data_in) > send_keep_alive(); > } > > I am not sure if that makes this function more legible, just food for thought. That would skip the EINTR continue, though since we're looping anyway here, I'm not sure it's a big deal. The biggest simplification is more like this, I think: diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e41f55f..8d792a2 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -328,6 +328,32 @@ static void rp_error(const char *err, ...) va_end(params); } +/* returns 1 if data is available, 0 otherwise */ +static int keepalive_poll(int in, int out) +{ + struct pollfd pfd; + int ret; + + pfd.fd = fd; + pfd.events = POLLIN; + ret = poll(, 1, 1000 * keepalive_in_sec); + + if (ret < 0) { + if (errno == EINTR) + return 0; /* no data, we should try again */ + else + return 1; /* no data, but read should return real error */ + } + if (ret == 0) { + /* no data; send a keepalive packet */ + static const char buf[] = "0005\1"; + write_or_die(out, buf, sizeof(buf) - 1); + return 0; + } + /* else there is actual data to read */ + return 1; +} + static int copy_to_sideband(int in, int out, void *arg) { char data[128]; @@ -341,26 +367,8 @@ static int copy_to_sideband(int in, int out, void *arg) while (1) { ssize_t sz; - if (keepalive_active) { - struct pollfd pfd; - int ret; - - pfd.fd = in; - pfd.events = POLLIN; - ret = poll(, 1, 1000 * keepalive_in_sec); - - if (ret < 0) { - if (errno == EINTR) - continue; - else - break; - } else if (ret == 0) { - /* no data; send a keepalive packet */ - static const char buf[] = "0005\1"; - write_or_die(1, buf, sizeof(buf) - 1); - continue; - } /* else there is actual data to read */ - } + if (keepalive_active && !keepalive_poll(in, 1)) + continue; sz = xread(in, data, sizeof(data)); if (sz <= 0) Note that there are actually three outcomes to poll (error, timeout, or data), and this simplifies away the "error" case to just "try to read some data" and assumes that the read() will cover (which is similar to what xread() does). I dunno if it really helps much, though. There's still a bunch of keepalive logic in the function to handle the NUL-signal case. I guess your proposal is more to have an "xpoll" that handles the EINTR looping, and leave that logic in place. I think that still leaves most of the complexity in the function. > > + } else if (ret == 0) { > > + /* no data; send a keepalive packet */ > > + static const char buf[] = "0005\1"; > > and the \1 is the first sideband. Why do we choose that sideband? What other sideband do you propose? :) More seriously, this is modeled on the similar keepalive in upload-pack. The only other option is sideband 2, and IIRC, clients do bad things with empty band-2 packets (like printing "remote: " but never finishing the line if we never
Re: [PATCH 12/12] receive-pack: send keepalives during quiet periods
Jeff Kingwrites: > diff --git a/builtin/index-pack.c b/builtin/index-pack.c > index 1cba120..54f2cfb 100644 > --- a/builtin/index-pack.c > +++ b/builtin/index-pack.c > @@ -1626,6 +1626,7 @@ int cmd_index_pack(int argc, const char **argv, const > char *prefix) > struct pack_idx_option opts; > unsigned char pack_sha1[20]; > unsigned foreign_nr = 1;/* zero is a "good" value, assume bad */ > + int report_end_of_input = 0; > > if (argc == 2 && !strcmp(argv[1], "-h")) > usage(index_pack_usage); > @@ -1697,6 +1698,8 @@ int cmd_index_pack(int argc, const char **argv, const > char *prefix) > verbose = 1; > } else if (!strcmp(arg, "--show-resolving-progress")) { > show_resolving_progress = 1; > + } else if (!strcmp(arg, "--report-end-of-input")) { > + report_end_of_input = 1; > } else if (!strcmp(arg, "-o")) { > if (index_name || (i+1) >= argc) > usage(index_pack_usage); > @@ -1754,6 +1757,8 @@ int cmd_index_pack(int argc, const char **argv, const > char *prefix) > obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct > object_stat)); > ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); > parse_pack_objects(pack_sha1); > + if (report_end_of_input) > + write_in_full(2, "\0", 1); > resolve_deltas(); > conclude_pack(fix_thin_pack, curr_pack, pack_sha1); > free(ofs_deltas); I naively thought "well, if we are teaching index-pack a new trick anyway, why not make it do the keepalive?", but that would not work because a keepalive is a 0-byte payload on band#1, i.e. "0005\1", and index-pack is not aware of the sideband framing at all. So I agree that a-nul-in-fd#2 is the best mechansim we can make it work. Thanks. -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH 12/12] receive-pack: send keepalives during quiet periods
On Fri, Jul 15, 2016 at 3:43 AM, Jeff Kingwrote: > > Signed-off-by: Jeff King Read-entirely-by Stefan ;) Thanks! > @@ -319,10 +331,60 @@ static void rp_error(const char *err, ...) > static int copy_to_sideband(int in, int out, void *arg) > { > char data[128]; While looking at this code, do you think it is feasible to increase the size of data[] to 1024 ? (The largest that is possible when side-band, but no side-band-64k is given). > + int keepalive_active = 0; > + > + if (keepalive_in_sec <= 0) > + use_keepalive = KEEPALIVE_NEVER; > + if (use_keepalive == KEEPALIVE_ALWAYS) > + keepalive_active = 1; > + > while (1) { > - ssize_t sz = xread(in, data, sizeof(data)); > + ssize_t sz; > + > + if (keepalive_active) { > + struct pollfd pfd; > + int ret; > + > + pfd.fd = in; > + pfd.events = POLLIN; > + ret = poll(, 1, 1000 * keepalive_in_sec); > + > + if (ret < 0) { > + if (errno == EINTR) > + continue; > + else > + break; The method was short and concise, this adds a lot of lines. Remembering d751dd11 (2016-07-10, hoist out handle_nonblock function for xread and xwrite), do you think it would be reasonable to put the whole poll handling into a dedicated function, maybe even reuse the that function? if (keepalive_active) { if (wrapper_around_poll(_in) < 0) // handles EINTR internally break; if (!data_in) send_keep_alive(); } I am not sure if that makes this function more legible, just food for thought. > + } else if (ret == 0) { > + /* no data; send a keepalive packet */ > + static const char buf[] = "0005\1"; and the \1 is the first sideband. Why do we choose that sideband? > + write_or_die(1, buf, sizeof(buf) - 1); > + continue; > + } /* else there is actual data to read */ "If there is data to read, we need to break the while(1), to actually read the data?" I got confused and needed to go back and read the actual code again, would it make sense to rather have a loop here? while (1) { while(keepalive_active) { if (wrapper_around_poll(_in) < 0) // handles EINTR internally break; if (!data_in) send_keep_alive(); else break; } sz = xread(in, data, sizeof(data)); if (sz <= 0) break; turn_on_keepalive_on_NUL(); } > + } > + > + sz = xread(in, data, sizeof(data)); > if (sz <= 0) > break; > + > + if (use_keepalive == KEEPALIVE_AFTER_NUL && > !keepalive_active) { > + const char *p = memchr(data, '\0', sz); > + if (p) { > + /* > +* The NUL tells us to start sending > keepalives. Make > +* sure we send any other data we read along > +* with it. > +*/ > + keepalive_active = 1; > + send_sideband(1, 2, data, p - data, > use_sideband); > + send_sideband(1, 2, p + 1, sz - (p - data + > 1), use_sideband); > + continue; Oh, I see why the turn_on_keepalive_on_NUL doesn't work as well as I thought. I wonder if we can use a better read function, that would stop reading at a NUL, and return early instead? -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
[PATCH 12/12] receive-pack: send keepalives during quiet periods
After a client has sent us the complete pack, we may spend some time processing the data and running hooks. If the client asked us to be quiet, receive-pack won't send any progress data during the index-pack or connectivity-check steps. And hooks may or may not produce their own progress output. In these cases, the network connection is totally silent from both ends. Git itself doesn't care about this (it will wait forever), but other parts of the system (e.g., firewalls, load-balancers, etc) might hang up the connection. So we'd like to send some sort of keepalive to let the network and the client side know that we're still alive and processing. We can use the same trick we did in 05e9515 (upload-pack: send keepalive packets during pack computation, 2013-09-08). Namely, we will send an empty sideband data packet every `N` seconds that we do not relay any stderr data over the sideband channel. As with 05e9515, this means that we won't bother sending keepalives when there's actual progress data, but will kick in when it has been disabled (or if there is a lull in the progress data). The concept is simple, but the details are subtle enough that they need discussing here. Before the client sends us the pack, we don't want to do any keepalives. We'll have sent our ref advertisement, and we're waiting for them to send us the pack (and tell us that they support sidebands at all). While we're receiving the pack from the client (or waiting for it to start), there's no need for keepalives; it's up to them to keep the connection active by sending data. Moreover, it would be wrong for us to do so. When we are the server in the smart-http protocol, we must treat our connection as half-duplex. So any keepalives we send while receiving the pack would potentially be buffered by the webserver. Not only does this make them useless (since they would not be delivered in a timely manner), but it could actually cause a deadlock if we fill up the buffer with keepalives. (It wouldn't be wrong to send keepalives in this phase for a full-duplex connection like ssh; it's simply pointless, as it is the client's responsibility to speak). As soon as we've gotten all of the pack data, then the client is waiting for us to speak, and we should start keepalives immediately. From here until the end of the connection, we send one any time we are not otherwise sending data. But there's a catch. Receive-pack doesn't know the moment we've gotten all the data. It passes the descriptor to index-pack, who reads all of the data, and then starts resolving the deltas. We have to communicate that back. To make this work, we instruct the sideband muxer to enable keepalives in three phases: 1. In the beginning, not at all. 2. While reading from index-pack, wait for a signal indicating end-of-input, and then start them. 3. Afterwards, always. The signal from index-pack in phase 2 has to come over the stderr channel which the muxer is reading. We can't use an extra pipe because the portable run-command interface only gives us stderr and stdout. Stdout is already used to pass the .keep filename back to receive-pack. We could also send a signal there, but then we would find out about it in the main thread. And the keepalive needs to be done by the async muxer thread (since it's the one writing sideband data back to the client). And we can't reliably signal the async thread from the main thread, because the async code sometimes uses threads and sometimes uses forked processes. Therefore the signal must come over the stderr channel, where it may be interspersed with other random human-readable messages from index-pack. This patch makes the signal a single NUL byte. This is easy to parse, should not appear in any normal stderr output, and we don't have to worry about any timing issues (like seeing half the signal bytes in one read(), and half in a subsequent one). This is a bit ugly, but it's simple to code and should work reliably. Another option would be to stop using an async thread for muxing entirely, and just poll() both stderr and stdout of index-pack from the main thread. This would work for index-pack (because we aren't doing anything useful in the main thread while it runs anyway). But it would make the connectivity check and the hook muxers much more complicated, as they need to simultaneously feed the sub-programs while reading their stderr. The index-pack phase is the only one that needs this signaling, so it could simply behave differently than the other two. That would mean having two separate implementations of copy_to_sideband (and the keepalive code), though. And it still doesn't get rid of the signaling; it just means we can write a nicer message like "END_OF_INPUT" or something on stdout, since we don't have to worry about separating it from the stderr cruft. One final note: this signaling trick is only done with index-pack, not with unpack-objects. There's no point in doing it for the latter, because by