upload-pack: adapt keepalives based on buffering

The function `create_pack_file()` is responsible for sending the packfile data to the client of git-upload-pack(1). As generating the bytes may take significant computing resources we also have a mechanism in place that optionally sends keepalive pktlines in case we haven't sent out any data. The keepalive logic is purely based poll(3p): we pass a timeout to that syscall, and if the call times out we send out the keepalive pktline. While reasonable, this logic isn't entirely sufficient: even if the call to poll(3p) ends because we have received data on any of the file descriptors we may not necessarily send data to the client. The most important edge case here happens in `relay_pack_data()`. When we haven't seen the initial "PACK" signature from git-pack-objects(1) yet we buffer incoming data. So in the worst case, if each of the bytes of that signature arrive shortly before the configured keepalive timeout, then we may not send out any data for a time period that is (almost) four times as long as the configured timeout. This edge case is rather unlikely to matter in practice. But in a subsequent commit we're going to adapt our buffering mechanism to become more aggressive, which makes it more likely that we don't send any data for an extended amount of time. Adapt the logic so that instead of using a fixed timeout on every call to poll(3p), we instead figure out how much time has passed since the last-sent data. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Mar 13, 2026 at 07:45 UTC 5d4b7ddce15950ed71ec60453aadfe13d9e633d5
1 file changed +40 -9
upload-pack.c
+40 -9
@@ -29,6 +29,7 @@
29 #include "commit-graph.h"
30 #include "commit-reach.h"
31 #include "shallow.h"
32 +#include "trace.h"
33 #include "write-or-die.h"
34 #include "json-writer.h"
35 #include "strmap.h"
@@ -218,7 +219,8 @@ struct output_state {
219 };
220
221 static int relay_pack_data(int pack_objects_out, struct output_state *os,
221 - int use_sideband, int write_packfile_line)
222 + int use_sideband, int write_packfile_line,
223 + bool *did_send_data)
224 {
225 /*
226 * We keep the last byte to ourselves
@@ -232,6 +234,8 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os,
234 */
235 ssize_t readsz;
236
237 + *did_send_data = false;
238 +
239 readsz = xread(pack_objects_out, os->buffer + os->used,
240 sizeof(os->buffer) - os->used);
241 if (readsz < 0) {
@@ -247,6 +251,7 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os,
251 if (os->packfile_uris_started)
252 packet_delim(1);
253 packet_write_fmt(1, "\1packfile\n");
254 + *did_send_data = true;
255 }
256 break;
257 }
@@ -259,6 +264,7 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os,
264 }
265 *p = '\0';
266 packet_write_fmt(1, "\1%s\n", os->buffer);
267 + *did_send_data = true;
268
269 os->used -= p - os->buffer + 1;
270 memmove(os->buffer, p + 1, os->used);
@@ -279,6 +285,7 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os,
285 os->used = 0;
286 }
287
288 + *did_send_data = true;
289 return readsz;
290 }
291
@@ -290,6 +297,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
297 char progress[128];
298 char abort_msg[] = "aborting due to possible repository "
299 "corruption on the remote side.";
300 + uint64_t last_sent_ms = 0;
301 ssize_t sz;
302 int i;
303 FILE *pipe_fd;
@@ -365,10 +373,14 @@ static void create_pack_file(struct upload_pack_data *pack_data,
373 */
374
375 while (1) {
376 + uint64_t now_ms = getnanotime() / 1000000;
377 struct pollfd pfd[2];
369 - int pe, pu, pollsize, polltimeout;
378 + int pe, pu, pollsize, polltimeout_ms;
379 int ret;
380
381 + if (!last_sent_ms)
382 + last_sent_ms = now_ms;
383 +
384 reset_timeout(pack_data->timeout);
385
386 pollsize = 0;
@@ -390,11 +402,21 @@ static void create_pack_file(struct upload_pack_data *pack_data,
402 if (!pollsize)
403 break;
404
393 - polltimeout = pack_data->keepalive < 0
394 - ? -1
395 - : 1000 * pack_data->keepalive;
405 + if (pack_data->keepalive < 0) {
406 + polltimeout_ms = -1;
407 + } else {
408 + /*
409 + * The polling timeout needs to be adjusted based on
410 + * the time we have sent our last package. The longer
411 + * it's been in the past, the shorter the timeout
412 + * becomes until we eventually don't block at all.
413 + */
414 + polltimeout_ms = 1000 * pack_data->keepalive - (now_ms - last_sent_ms);
415 + if (polltimeout_ms < 0)
416 + polltimeout_ms = 0;
417 + }
418
397 - ret = poll(pfd, pollsize, polltimeout);
419 + ret = poll(pfd, pollsize, polltimeout_ms);
420
421 if (ret < 0) {
422 if (errno != EINTR) {
@@ -403,16 +425,18 @@ static void create_pack_file(struct upload_pack_data *pack_data,
425 }
426 continue;
427 }
428 +
429 if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
430 /* Status ready; we ship that in the side-band
431 * or dump to the standard error.
432 */
433 sz = xread(pack_objects.err, progress,
434 sizeof(progress));
412 - if (0 < sz)
435 + if (0 < sz) {
436 send_client_data(2, progress, sz,
437 pack_data->use_sideband);
415 - else if (sz == 0) {
438 + last_sent_ms = now_ms;
439 + } else if (sz == 0) {
440 close(pack_objects.err);
441 pack_objects.err = -1;
442 }
@@ -421,11 +445,14 @@ static void create_pack_file(struct upload_pack_data *pack_data,
445 /* give priority to status messages */
446 continue;
447 }
448 +
449 if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
450 + bool did_send_data;
451 int result = relay_pack_data(pack_objects.out,
452 output_state,
453 pack_data->use_sideband,
428 - !!uri_protocols);
454 + !!uri_protocols,
455 + &did_send_data);
456
457 if (result == 0) {
458 close(pack_objects.out);
@@ -433,6 +460,9 @@ static void create_pack_file(struct upload_pack_data *pack_data,
460 } else if (result < 0) {
461 goto fail;
462 }
463 +
464 + if (did_send_data)
465 + last_sent_ms = now_ms;
466 }
467
468 /*
@@ -448,6 +478,7 @@ static void create_pack_file(struct upload_pack_data *pack_data,
478 if (!ret && pack_data->use_sideband) {
479 static const char buf[] = "0005\1";
480 write_or_die(1, buf, 5);
481 + last_sent_ms = now_ms;
482 }
483 }
484