Commit 9ccdb21155b3c9650acf58c55b2596c3503ea14d

Brodie Rao 2013-08-13T10:55:37

fileops: stat() before open()ing in git_futils_readbuffer_updated() This reverts refactoring done in 13224ea4aad9a1b3c9cc4c992ceaea9af623e047 that introduces a performance regression for NFS when reading files that don't exist. open() forces a cache invalidation on NFS, while stat()ing a file just uses the cache and is very quick. To give a specific example, say you have a repo with a thousand packed refs. Before this change, looking up every single one ould incur a thousand slow open() calls. With this change, it's a thousand fast stat() calls.

diff --git a/src/fileops.c b/src/fileops.c
index 7f8418d..e367ac2 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -147,6 +147,7 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
 int git_futils_readbuffer_updated(
 	git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)
 {
+	int error = 0;
 	git_file fd;
 	struct stat st;
 	bool changed = false;
@@ -156,11 +157,15 @@ int git_futils_readbuffer_updated(
 	if (updated != NULL)
 		*updated = 0;
 
-	if ((fd = git_futils_open_ro(path)) < 0)
-		return fd;
+	if (p_stat(path, &st) < 0) {
+		error = errno;
+		giterr_set(GITERR_OS, "Failed to stat '%s'", path);
+		if (error == ENOENT || error == ENOTDIR)
+			return GIT_ENOTFOUND;
+		return -1;
+	}
 
-	if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) {
-		p_close(fd);
+	if (S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) {
 		giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path);
 		return -1;
 	}
@@ -177,7 +182,6 @@ int git_futils_readbuffer_updated(
 		changed = true;
 
 	if (!changed) {
-		p_close(fd);
 		return 0;
 	}
 
@@ -186,6 +190,9 @@ int git_futils_readbuffer_updated(
 	if (size != NULL)
 		*size = (size_t)st.st_size;
 
+	if ((fd = git_futils_open_ro(path)) < 0)
+		return fd;
+
 	if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
 		p_close(fd);
 		return -1;