git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH/WIP v2 04/14] read-cache: ask file watcher to watch files

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Jan 17, 2014, 09:47 UTC
Message-ID
<1389952060-12297-5-git-send-email-pclouds@gmail.com>
In-Reply-To
<1389952060-12297-1-git-send-email-pclouds@gmail.com>

We want to watch files that are never changed because lstat() on those files is a wasted effort. So we sort unwatched files by date and start adding them to the file watcher until it barfs (e.g. hits inotify limit). Recently updated entries are also excluded from watch list. CE_VALID is used in combination with CE_WATCHED. Those entries that have CE_VALID already set will never be watched.

We send as many paths as possible in one packet in pkt-line format to reduce roundtrips. For small projects like git, all entries can be packed in one packet. For large projects like webkit (182k entries) it takes two packets. We may do prefix compression as well to send more in fewer packets..

The file watcher replies how many entries it can watch (because at least inotify has system limits).

Note that we still do lstat() on these new watched files because they could have changed before the file watcher could watch them. Watched files may only skip lstat() at the next git run.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 file-watcher.c |  31 ++++++++++++++++
 pkt-line.c     |   2 +-
 pkt-line.h     |   2 ++
 read-cache.c   | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 4 files changed, 143 insertions(+), 3 deletions(-)
diff --git a/file-watcher.c b/file-watcher.c
index 36a9a8d..3a54168 100644
--- a/file-watcher.c
+++ b/file-watcher.c
@@ -3,6 +3,7 @@
 #include "parse-options.h"
 #include "exec_cmd.h"
 #include "file-watcher-lib.h"
+#include "pkt-line.h"
 
 static const char *const file_watcher_usage[] = {
 	N_("git file-watcher [options]"),
@@ -11,6 +12,34 @@ static const char *const file_watcher_usage[] = {
 
 static char index_signature[41];
 
+static int watch_path(char *path)
+{
+	/*
+	 * Consider send "wait" every 10ms or so, in case there are
+	 * many paths to process that takes more than 20ms or the
+	 * sender won't keep waiting. This is usually one-time cost,
+	 * waiting a bit is ok.
+	 */
+	return -1;
+}
+
+static void watch_paths(char *buf, int maxlen,
+			int fd, struct sockaddr_un *sock)
+{
+	char *end = buf + maxlen;
+	int n, ret, len;
+	for (n = ret = 0; buf < end && !ret; buf += len) {
+		char ch;
+		len = packet_length(buf);
+		ch = buf[len];
+		buf[len] = '\0';
+		if (!(ret = watch_path(buf + 4)))
+			n++;
+		buf[len] = ch;
+	}
+	send_watcher(fd, sock, "fine %d", n);
+}
+
 static int handle_command(int fd)
 {
 	struct sockaddr_un sun;
@@ -29,6 +58,8 @@ static int handle_command(int fd)
 			 * wrong. Clean up and start over.
 			 */
 			index_signature[0] = '\0';
+	} else if (starts_with(msg, "watch ")) {
+		watch_paths(msg + 6, len - 6, fd, &sun);
 	} else if (!strcmp(msg, "die")) {
 		exit(0);
 	} else {
diff --git a/pkt-line.c b/pkt-line.c
index bc63b3b..b5af84e 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -135,7 +135,7 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,
 	return ret;
 }
 
-static int packet_length(const char *linelen)
+int packet_length(const char *linelen)
 {
 	int n;
 	int len = 0;
diff --git a/pkt-line.h b/pkt-line.h
index 0a838d1..40470b9 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -75,6 +75,8 @@ char *packet_read_line(int fd, int *size);
  */
 char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);
 
+int packet_length(const char *linelen);
+
 #define DEFAULT_PACKET_MAX 1000
 #define LARGE_PACKET_MAX 65520
 extern char packet_buffer[LARGE_PACKET_MAX];
diff --git a/read-cache.c b/read-cache.c
index 76cf0e3..21c3207 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -15,6 +15,7 @@
 #include "strbuf.h"
 #include "varint.h"
 #include "file-watcher-lib.h"
+#include "pkt-line.h"
 
 static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 
@@ -1479,6 +1480,98 @@ static void validate_watcher(struct index_state *istate, const char *path)
 		}
 }
 
+static int sort_by_date(const void *a_, const void *b_)
+{
+	const struct cache_entry *a = *(const struct cache_entry **)a_;
+	const struct cache_entry *b = *(const struct cache_entry **)b_;
+	uint32_t seca = a->ce_stat_data.sd_mtime.sec;
+	uint32_t secb = b->ce_stat_data.sd_mtime.sec;
+	return seca - secb;
+}
+
+static int do_watch_entries(struct index_state *istate,
+			    struct cache_entry **cache,
+			    struct strbuf *sb, int start, int now)
+{
+	char *line;
+	int i;
+	ssize_t len;
+
+	send_watcher(istate->watcher, NULL, "%s", sb->buf);
+	line = read_watcher(istate->watcher, &len, NULL);
+	if (!line) {
+		if (!len) {
+			close(istate->watcher);
+			istate->watcher = -1;
+		}
+		return -1;
+	}
+	if (starts_with(line, "fine ")) {
+		char *end;
+		long n = strtoul(line + 5, &end, 10);
+		if (end != line + len)
+			return -1;
+		for (i = 0; i < n; i++)
+			cache[start + i]->ce_flags |= CE_WATCHED;
+		istate->cache_changed = 1;
+		if (i != now)
+			return -1;
+	} else
+		return -1;
+	start = i;
+	strbuf_reset(sb);
+	strbuf_addstr(sb, "watch ");
+	return 0;
+}
+
+static inline int ce_watchable(struct cache_entry *ce)
+{
+	return ce_uptodate(ce) && /* write_index will catch late ce_uptodate bits */
+		!(ce->ce_flags & CE_WATCHED) &&
+		!(ce->ce_flags & CE_VALID) &&
+		/*
+		 * S_IFGITLINK should not be watched
+		 * obviously. S_IFLNK could be problematic because
+		 * inotify may follow symlinks without IN_DONT_FOLLOW
+		 */
+		S_ISREG(ce->ce_mode);
+}
+
+static void watch_entries(struct index_state *istate)
+{
+	int i, start, nr;
+	struct cache_entry **sorted;
+	struct strbuf sb = STRBUF_INIT;
+	int val;
+	socklen_t vallen = sizeof(val);
+
+	if (istate->watcher <= 0)
+		return;
+	for (i = nr = 0; i < istate->cache_nr; i++)
+		if (ce_watchable(istate->cache[i]))
+			nr++;
+	sorted = xmalloc(sizeof(*sorted) * nr);
+	for (i = nr = 0; i < istate->cache_nr; i++)
+		if (ce_watchable(istate->cache[i]))
+			sorted[nr++] = istate->cache[i];
+
+	getsockopt(istate->watcher, SOL_SOCKET, SO_SNDBUF, &val, &vallen);
+	strbuf_grow(&sb, val);
+	strbuf_addstr(&sb, "watch ");
+
+	qsort(sorted, nr, sizeof(*sorted), sort_by_date);
+	for (i = start = 0; i < nr; i++) {
+		if (sb.len + 4 + ce_namelen(sorted[i]) >= val &&
+		    do_watch_entries(istate, sorted, &sb, start, i))
+			break;
+		packet_buf_write(&sb, "%s", sorted[i]->name);
+	}
+	if (i == nr && start < i)
+		do_watch_entries(istate, sorted, &sb, start, i);
+	strbuf_release(&sb);
+	free(sorted);
+}
+
 /* remember to discard_cache() before reading a different cache! */
 int read_index_from(struct index_state *istate, const char *path)
 {
@@ -1565,6 +1658,7 @@ int read_index_from(struct index_state *istate, const char *path)
 	}
 	munmap(mmap, mmap_size);
 	validate_watcher(istate, path);
+	watch_entries(istate);
 	return istate->cache_nr;
 
 unmap:
@@ -1844,8 +1938,21 @@ int write_index(struct index_state *istate, int newfd)
 	for (i = removed = extended = 0; i < entries; i++) {
 		if (cache[i]->ce_flags & CE_REMOVE)
 			removed++;
-		else if (cache[i]->ce_flags & CE_WATCHED)
-			has_watches++;
+		else if (cache[i]->ce_flags & CE_WATCHED) {
+			/*
+			 * We may set CE_WATCHED (but not CE_VALID)
+			 * early when refresh has not been done
+			 * yet. At that time we had no idea if the
+			 * entry may have been updated. If it has
+			 * been, remove CE_WATCHED so CE_VALID won't
+			 * incorrectly be set next time if the file
+			 * watcher reports no changes.
+			 */
+			if (!ce_uptodate(cache[i]))
+				cache[i]->ce_flags &= ~CE_WATCHED;
+			else
+				has_watches++;
+		}
 
 		/* reduce extended entries if possible */
 		cache[i]->ce_flags &= ~CE_EXTENDED;
-- 
1.8.5.1.208.g05b12ea
Previous: Duy NguyenNext: Nguyễn Thái Ngọc Duy
Message 21 of 72 in “inotify support”
  1. 0/6 inotify supportNguyễn Thái Ngọc Duy, Jan 12, 2014
  2. 1/6 read-cache: save trailing sha-1Nguyễn Thái Ngọc Duy, Jan 12, 2014
  3. 2/6 read-cache: new extension to mark what file is watchedNguyễn Thái Ngọc Duy, Jan 12, 2014
  4. Jonathan NiederJan 13, 2014
  5. Duy NguyenJan 14, 2014
  6. Duy NguyenJan 14, 2014
  7. 3/6 read-cache: connect to file watcherNguyễn Thái Ngọc Duy, Jan 12, 2014
  8. Jeff KingJan 15, 2014
  9. 4/6 read-cache: get "updated" path list from file watcherNguyễn Thái Ngọc Duy, Jan 12, 2014
  10. 5/6 read-cache: ask file watcher to watch filesNguyễn Thái Ngọc Duy, Jan 12, 2014
  11. 6/6 file-watcher: support inotifyNguyễn Thái Ngọc Duy, Jan 12, 2014
  12. 00/14 inotify supportNguyễn Thái Ngọc Duy, Jan 17, 2014
  13. 01/14 read-cache: save trailing sha-1Nguyễn Thái Ngọc Duy, Jan 17, 2014
  14. 02/14 read-cache: new extension to mark what file is watchedNguyễn Thái Ngọc Duy, Jan 17, 2014
  15. Thomas GummererJan 17, 2014
  16. Thomas RastJan 19, 2014
  17. Duy NguyenJan 20, 2014
  18. 03/14 read-cache: connect to file watcherNguyễn Thái Ngọc Duy, Jan 17, 2014
  19. Torsten BögershausenJan 17, 2014
  20. Duy NguyenJan 17, 2014
  21. 04/14 read-cache: ask file watcher to watch filesNguyễn Thái Ngọc Duy, Jan 17, 2014
  22. 05/14 read-cache: put some limits on file watchingNguyễn Thái Ngọc Duy, Jan 17, 2014
  23. Thomas RastJan 19, 2014
  24. Duy NguyenJan 20, 2014
  25. 06/14 read-cache: get modified file list from file watcherNguyễn Thái Ngọc Duy, Jan 17, 2014
  26. 07/14 read-cache: add config to start file watcher automaticallyNguyễn Thái Ngọc Duy, Jan 17, 2014
  27. 08/14 read-cache: add GIT_TEST_FORCE_WATCHER for testingNguyễn Thái Ngọc Duy, Jan 17, 2014
  28. Thomas RastJan 19, 2014
  29. Duy NguyenJan 20, 2014
  30. 09/14 file-watcher: add --shutdown and --log optionsNguyễn Thái Ngọc Duy, Jan 17, 2014
  31. 10/14 file-watcher: automatically quitNguyễn Thái Ngọc Duy, Jan 17, 2014
  32. 11/14 file-watcher: support inotifyNguyễn Thái Ngọc Duy, Jan 17, 2014
  33. Thomas RastJan 19, 2014
  34. Duy NguyenJan 20, 2014
  35. Thomas RastJan 20, 2014
  36. Duy NguyenJan 28, 2014
  37. 00/26 inotify supportNguyễn Thái Ngọc Duy, Feb 3, 2014
  38. 01/26 pkt-line.c: rename global variable buffer[] to something less genericNguyễn Thái Ngọc Duy, Feb 3, 2014
  39. 02/26 pkt-line.c: add packet_write_timeout()Nguyễn Thái Ngọc Duy, Feb 3, 2014
  40. 03/26 pkt-line.c: add packet_read_line_timeout()Nguyễn Thái Ngọc Duy, Feb 3, 2014
  41. 04/26 unix-socket: make unlink() optional in unix_stream_listen()Nguyễn Thái Ngọc Duy, Feb 3, 2014
  42. 05/26 Add git-file-watcher and basic connection handling logicNguyễn Thái Ngọc Duy, Feb 3, 2014
  43. 06/26 file-watcher: check socket directory permissionNguyễn Thái Ngọc Duy, Feb 3, 2014
  44. 07/26 file-watcher: remove socket on exitNguyễn Thái Ngọc Duy, Feb 3, 2014
  45. 08/26 file-watcher: add --detachNguyễn Thái Ngọc Duy, Feb 3, 2014
  46. 09/26 read-cache: save trailing sha-1Nguyễn Thái Ngọc Duy, Feb 3, 2014
  47. 10/26 read-cache: new flag CE_WATCHED to mark what file is watchedNguyễn Thái Ngọc Duy, Feb 3, 2014
  48. 11/26 Clear CE_WATCHED when set CE_VALID aloneNguyễn Thái Ngọc Duy, Feb 3, 2014
  49. 12/26 read-cache: basic hand shaking to the file watcherNguyễn Thái Ngọc Duy, Feb 3, 2014
  50. 13/26 read-cache: ask file watcher to watch filesNguyễn Thái Ngọc Duy, Feb 3, 2014
  51. 14/26 read-cache: put some limits on file watchingNguyễn Thái Ngọc Duy, Feb 3, 2014
  52. 15/26 read-cache: get changed file list from file watcherNguyễn Thái Ngọc Duy, Feb 3, 2014
  53. 16/26 git-compat-util.h: add inotify stubs on non-Linux platformsNguyễn Thái Ngọc Duy, Feb 3, 2014
  54. 17/26 file-watcher: inotify support, watching partNguyễn Thái Ngọc Duy, Feb 3, 2014
  55. 18/26 file-watcher: inotify support, notification partNguyễn Thái Ngọc Duy, Feb 3, 2014
  56. 19/26 Wrap CE_VALID test with ce_valid()Nguyễn Thái Ngọc Duy, Feb 3, 2014
  57. 20/26 read-cache: new variable to verify file-watcher resultsNguyễn Thái Ngọc Duy, Feb 3, 2014
  58. 21/26 Support running file watcher with the test suiteNguyễn Thái Ngọc Duy, Feb 3, 2014
  59. 22/26 file-watcher: quit if $WATCHER/socket is goneNguyễn Thái Ngọc Duy, Feb 3, 2014
  60. 23/26 file-watcher: tests for the daemonNguyễn Thái Ngọc Duy, Feb 3, 2014
  61. 24/26 ls-files: print CE_WATCHED as W (or "w" with CE_VALID)Nguyễn Thái Ngọc Duy, Feb 3, 2014
  62. 25/26 file-watcher: tests for the client sideNguyễn Thái Ngọc Duy, Feb 3, 2014
  63. 26/26 Disable file-watcher with system inotify on some testsNguyễn Thái Ngọc Duy, Feb 3, 2014
  64. Torsten BögershausenFeb 8, 2014
  65. Duy NguyenFeb 8, 2014
  66. Torsten BögershausenFeb 9, 2014
  67. Duy NguyenFeb 10, 2014
  68. Torsten BögershausenFeb 10, 2014
  69. Duy NguyenFeb 10, 2014
  70. Duy NguyenFeb 17, 2014
  71. Shawn PearceFeb 19, 2014
  72. Duy NguyenFeb 19, 2014

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.