mirror of
https://github.com/git/git.git
synced 2025-12-12 20:36:24 +01:00
Using writev here is 20-40% faster than three write syscalls in succession for smaller (1-10k) objects in the delta base cache. This advantage decreases as object sizes approach pipe size (64k on Linux). writev reduces wakeups and syscalls on the read side as well: each write(2) syscall may trigger one or more corresponding read(2) syscalls in the reader. Attempting atomicity in the writer via writev also reduces the likelyhood of non-blocking readers failing with EAGAIN and having to call poll||select before attempting to read again. Unfortunately, this turns into a small (1-3%) slowdown for gigantic objects of a megabyte or more even with after increasing pipe size to 1MB via the F_SETPIPE_SZ fcntl(2) op. This slowdown is acceptable to me since the vast majority of objects are 64K or less for projects I've looked at. Relying on stdio buffering and fflush(3) after each response was considered for users without --buffer, but historically cat-file defaults to being compatible with non-blocking stdout and able to poll(2) after hitting EAGAIN on write(2). Using stdio on files with the O_NONBLOCK flag is (AFAIK) unspecified and likely subject to portability problems and thus avoided. Signed-off-by: Eric Wong <e@80x24.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
176 lines
3.9 KiB
C
176 lines
3.9 KiB
C
#include "git-compat-util.h"
|
|
#include "parse.h"
|
|
#include "run-command.h"
|
|
#include "write-or-die.h"
|
|
|
|
/*
|
|
* Some cases use stdio, but want to flush after the write
|
|
* to get error handling (and to get better interactive
|
|
* behaviour - not buffering excessively).
|
|
*
|
|
* Of course, if the flush happened within the write itself,
|
|
* we've already lost the error code, and cannot report it any
|
|
* more. So we just ignore that case instead (and hope we get
|
|
* the right error code on the flush).
|
|
*
|
|
* If the file handle is stdout, and stdout is a file, then skip the
|
|
* flush entirely since it's not needed.
|
|
*/
|
|
void maybe_flush_or_die(FILE *f, const char *desc)
|
|
{
|
|
if (f == stdout) {
|
|
static int force_flush_stdout = -1;
|
|
|
|
if (force_flush_stdout < 0) {
|
|
force_flush_stdout = git_env_bool("GIT_FLUSH", -1);
|
|
if (force_flush_stdout < 0) {
|
|
struct stat st;
|
|
if (fstat(fileno(stdout), &st))
|
|
force_flush_stdout = 1;
|
|
else
|
|
force_flush_stdout = !S_ISREG(st.st_mode);
|
|
}
|
|
}
|
|
if (!force_flush_stdout && !ferror(f))
|
|
return;
|
|
}
|
|
if (fflush(f)) {
|
|
check_pipe(errno);
|
|
die_errno("write failure on '%s'", desc);
|
|
}
|
|
}
|
|
|
|
void fprintf_or_die(FILE *f, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
va_start(ap, fmt);
|
|
ret = vfprintf(f, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (ret < 0) {
|
|
check_pipe(errno);
|
|
die_errno("write error");
|
|
}
|
|
}
|
|
|
|
static int maybe_fsync(int fd)
|
|
{
|
|
if (use_fsync < 0)
|
|
use_fsync = git_env_bool("GIT_TEST_FSYNC", 1);
|
|
if (!use_fsync)
|
|
return 0;
|
|
|
|
if (fsync_method == FSYNC_METHOD_WRITEOUT_ONLY &&
|
|
git_fsync(fd, FSYNC_WRITEOUT_ONLY) >= 0)
|
|
return 0;
|
|
|
|
return git_fsync(fd, FSYNC_HARDWARE_FLUSH);
|
|
}
|
|
|
|
void fsync_or_die(int fd, const char *msg)
|
|
{
|
|
if (maybe_fsync(fd) < 0)
|
|
die_errno("fsync error on '%s'", msg);
|
|
}
|
|
|
|
int fsync_component(enum fsync_component component, int fd)
|
|
{
|
|
if (fsync_components & component)
|
|
return maybe_fsync(fd);
|
|
return 0;
|
|
}
|
|
|
|
void fsync_component_or_die(enum fsync_component component, int fd, const char *msg)
|
|
{
|
|
if (fsync_components & component)
|
|
fsync_or_die(fd, msg);
|
|
}
|
|
|
|
void write_or_die(int fd, const void *buf, size_t count)
|
|
{
|
|
if (write_in_full(fd, buf, count) < 0) {
|
|
check_pipe(errno);
|
|
die_errno("write error");
|
|
}
|
|
}
|
|
|
|
void fwrite_or_die(FILE *f, const void *buf, size_t count)
|
|
{
|
|
if (fwrite(buf, 1, count, f) != count)
|
|
die_errno("fwrite error");
|
|
}
|
|
|
|
void fflush_or_die(FILE *f)
|
|
{
|
|
if (fflush(f))
|
|
die_errno("fflush error");
|
|
}
|
|
|
|
void fwritev_or_die(FILE *fp, const struct git_iovec *iov, int iovcnt)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < iovcnt; i++) {
|
|
size_t n = iov[i].iov_len;
|
|
|
|
if (fwrite(iov[i].iov_base, 1, n, fp) != n)
|
|
die_errno("unable to write to FD=%d", fileno(fp));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* note: we don't care about atomicity from writev(2) right now.
|
|
* The goal is to avoid allocations+copies in the writer and
|
|
* reduce wakeups+syscalls in the reader.
|
|
* n.b. @iov is not const since we modify it to avoid allocating
|
|
* on partial write.
|
|
*/
|
|
#ifdef HAVE_WRITEV
|
|
void writev_or_die(int fd, struct git_iovec *iov, int iovcnt)
|
|
{
|
|
int i;
|
|
|
|
while (iovcnt > 0) {
|
|
ssize_t n = xwritev(fd, iov, iovcnt);
|
|
|
|
/* EINVAL happens when sum of iov_len exceeds SSIZE_MAX */
|
|
if (n < 0 && errno == EINVAL)
|
|
n = xwrite(fd, iov[0].iov_base, iov[0].iov_len);
|
|
if (n < 0) {
|
|
check_pipe(errno);
|
|
die_errno("writev error");
|
|
} else if (!n) {
|
|
errno = ENOSPC;
|
|
die_errno("writev_error");
|
|
}
|
|
/* skip fully written iovs, retry from the first partial iov */
|
|
for (i = 0; i < iovcnt; i++) {
|
|
if (n >= iov[i].iov_len) {
|
|
n -= iov[i].iov_len;
|
|
} else {
|
|
iov[i].iov_len -= n;
|
|
iov[i].iov_base = (char *)iov[i].iov_base + n;
|
|
break;
|
|
}
|
|
}
|
|
iovcnt -= i;
|
|
iov += i;
|
|
}
|
|
}
|
|
#else /* !HAVE_WRITEV */
|
|
|
|
/*
|
|
* n.b. don't use stdio fwrite here even if it's faster, @fd may be
|
|
* non-blocking and stdio isn't equipped for EAGAIN
|
|
*/
|
|
void writev_or_die(int fd, struct git_iovec *iov, int iovcnt)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < iovcnt; i++)
|
|
write_or_die(fd, iov[i].iov_base, iov[i].iov_len);
|
|
}
|
|
#endif /* !HAVE_WRITEV */
|