diff --git a/cache.c b/cache.c index e70af13..9c36f1a 100644 --- a/cache.c +++ b/cache.c @@ -97,6 +97,7 @@ static int print_slot(struct cache_slot *slot) do { ssize_t ret; + html_flush(); ret = sendfile(STDOUT_FILENO, slot->cache_fd, &off, size - off); if (ret < 0) { if (errno == EAGAIN || errno == EINTR) @@ -121,6 +122,7 @@ static int print_slot(struct cache_slot *slot) return errno; if (ret == 0) return 0; + html_flush(); if (write_in_full(STDOUT_FILENO, slot->buf, ret) < 0) return errno; } while (1); @@ -207,6 +209,7 @@ static int unlock_slot(struct cache_slot *slot, int replace_old_slot) /* Restore stdout and close the temporary FD. */ if (slot->stdout_fd >= 0) { + html_flush(); dup2(slot->stdout_fd, STDOUT_FILENO); close(slot->stdout_fd); slot->stdout_fd = -1; @@ -224,6 +227,7 @@ static int unlock_slot(struct cache_slot *slot, int replace_old_slot) static int fill_slot(struct cache_slot *slot) { /* Preserve stdout */ + html_flush(); slot->stdout_fd = dup(STDOUT_FILENO); if (slot->stdout_fd == -1) return errno; @@ -235,7 +239,10 @@ static int fill_slot(struct cache_slot *slot) /* Generate cache content */ slot->fn(); - /* Make sure any buffered data is flushed to the file */ + /* Make sure any buffered data is flushed to the file. This must + * happen before the fstat() below, which records the size used to + * serve the slot. */ + html_flush(); if (fflush(stdout)) return errno; diff --git a/cgit.c b/cgit.c index a6ed39f..65e369c 100644 --- a/cgit.c +++ b/cgit.c @@ -686,6 +686,7 @@ static inline void authenticate_post(void) len = MAX_AUTHENTICATION_POST_BYTES; if ((len = read(STDIN_FILENO, buffer, len)) < 0) die_errno("Could not read POST from stdin"); + html_flush(); if (write(STDOUT_FILENO, buffer, len) < 0) die_errno("Could not write POST to stdout"); cgit_close_filter(ctx.cfg.auth_filter); @@ -1078,6 +1079,7 @@ int cmd_main(int argc, const char **argv) int err, ttl; cgit_init_filters(); + atexit(html_flush); atexit(cgit_cleanup_filters); set_die_routine(cgit_die_routine); diff --git a/filter.c b/filter.c index c778d05..6b70cc2 100644 --- a/filter.c +++ b/filter.c @@ -48,6 +48,7 @@ static int open_exec_filter(struct cgit_filter *base, va_list ap) for (i = 0; i < filter->base.argument_count; i++) filter->argv[i + 1] = va_arg(ap, char *); + html_flush(); filter->old_stdout = chk_positive(dup(STDOUT_FILENO), "Unable to duplicate STDOUT"); chk_zero(pipe(pipe_fh), "Unable to create pipe to subprocess"); @@ -71,6 +72,7 @@ static int close_exec_filter(struct cgit_filter *base) struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base; int i, exit_status = 0; + html_flush(); chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), "Unable to restore STDOUT"); close(filter->old_stdout); diff --git a/html.c b/html.c index 0bac34b..1db7a17 100644 --- a/html.c +++ b/html.c @@ -78,12 +78,46 @@ char *fmtalloc(const char *format, ...) return strbuf_detach(&sb, NULL); } -void html_raw(const char *data, size_t size) +/* + * Output is accumulated here and flushed in large chunks. cgit emits HTML + * in very small pieces (a 1.7MB diff is ~99k html() calls), and one write() + * per piece is almost all syscall overhead. Anything that redirects fd 1 + * (the cache slot, exec filters) must call html_flush() first, and so must + * any path that exits. + */ +#define HTML_BUFSZ (64 * 1024) +static char html_buf[HTML_BUFSZ]; +static size_t html_buflen; + +static void html_write(const char *data, size_t size) { - if (write(STDOUT_FILENO, data, size) != size) + if (write_in_full(STDOUT_FILENO, data, size) < 0) die_errno("write error on html output"); } +void html_flush(void) +{ + if (!html_buflen) + return; + /* Clear first: die_errno() unwinds through here again. */ + size_t n = html_buflen; + html_buflen = 0; + html_write(html_buf, n); +} + +void html_raw(const char *data, size_t size) +{ + if (size >= HTML_BUFSZ) { + html_flush(); + html_write(data, size); + return; + } + if (html_buflen + size > HTML_BUFSZ) + html_flush(); + memcpy(html_buf + html_buflen, data, size); + html_buflen += size; +} + void html(const char *txt) { html_raw(txt, strlen(txt)); diff --git a/html.h b/html.h index fa4de77..1d384d9 100644 --- a/html.h +++ b/html.h @@ -4,6 +4,7 @@ #include "cgit.h" extern void html_raw(const char *txt, size_t size); +extern void html_flush(void); extern void html(const char *txt); __attribute__((format (printf,1,2))) diff --git a/ui-shared.c b/ui-shared.c index e015590..cb306bc 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -769,6 +769,12 @@ void cgit_print_http_headers(void) if (ctx.page.etag) htmlf("ETag: \"%s\"\n", ctx.page.etag); html("\n"); + /* + * Flush here so the headers reach the client before the body, + * whoever writes it. Snapshots and clone data are produced by + * git writing straight to fd 1, bypassing html_raw(). + */ + html_flush(); if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) exit(0); }