[RFC/PATCH] core.clientTimeout config for git:// fetches

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This allows "git fetch" to exit after a preconfigured socket
timeout.  This is needed in case an undetected server or network
failure occurs.

The default timeout is 60 seconds.

http:// and rsync:// transports should be supported in future
changes, too.

So far, I've only tested (by hand) with a dummy server setup
using "nc -l $some_port".
---

  I run "git fetch" from cron to keep mirrors up-to-date.  I've
  noticed some cron processes running for hours/days at a time after
  network failures.  When I straced thoses processes, I saw they were
  stuck on read() syscalls to remote machines.

  I'm not familiar with this code and hope there's a better way to do
  this.  Changing wrapper.c like this in this way doesn't feel right,
  but neither does sprinkling alarm() calls in various places.

  Using a custom GIT_PROXY_COMMAND which implements timeouts on its own
  would also work, but I'd like to eventually have a consistent parameter
  unified across all remote transports for consistency and ease-of-use.

  The default 60s timeout could be too low on some slow servers
  with big repos, maybe the default should remain zero (no timeout).

 connect.c |   27 +++++++++++++++++++++++++++
 wrapper.c |   47 +++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/connect.c b/connect.c
index ee1d4b4..c1fa1ab 100644
--- a/connect.c
+++ b/connect.c
@@ -320,10 +320,37 @@ static int git_tcp_connect_sock(char *host, int flags)
 
 #endif /* NO_IPV6 */
 
+static int timeout_setup(const char *var, const char *value, void *cb)
+{
+	int *timeout = cb;
+
+	if (strcmp(var, "core.clienttimeout") == 0)
+		*timeout = git_config_int(var, value);
+
+	return 0;
+}
 
 static void git_tcp_connect(int fd[2], char *host, int flags)
 {
 	int sockfd = git_tcp_connect_sock(host, flags);
+	struct timeval tv;
+	int timeout = 60;
+
+	git_config(timeout_setup, &timeout);
+
+	if (timeout > 0) {
+		socklen_t len = sizeof(struct timeval);
+
+		tv.tv_sec = timeout;
+		tv.tv_usec = 0;
+		if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, len) < 0)
+			die_errno("could not set socket receive timeout");
+
+		tv.tv_sec = timeout;
+		tv.tv_usec = 0;
+		if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, len) < 0)
+			die_errno("could not set socket send timeout");
+	}
 
 	fd[0] = sockfd;
 	fd[1] = dup(sockfd);
diff --git a/wrapper.c b/wrapper.c
index 85f09df..09a31c6 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -109,6 +109,49 @@ void *xcalloc(size_t nmemb, size_t size)
 	return ret;
 }
 
+static int io_continuable(int fd, int optname)
+{
+	if (errno == EINTR)
+		return 1;
+
+	/*
+	 * If fd is a socket, a socket-level timeout could've been
+	 * set in git_tcp_connect().  Unfortunately EAGAIN alone
+	 * isn't enough to tell us if the timeout is user-specified
+	 * or not, so we have to try to figure it out.
+	 */
+	if (errno == EAGAIN) {
+		socklen_t len = sizeof(struct timeval);
+		struct timeval tv;
+		int flags;
+
+		/*
+		 * Let any non-blocking I/O users through.  Currently
+		 * git does not explicitly use non-blocking I/O anywhere,
+		 * but it could inherit a non-blocking descriptor.  We
+		 * rely on the fact that (currently) no sockets git itself
+		 * creates (and thus may set timeout on) also sets O_NONBLOCK.
+		 */
+		flags = fcntl(fd, F_GETFL);
+		if (flags < 0)
+			die_errno("fcntl failed to get file status flags");
+		if (flags & O_NONBLOCK)
+			return 1;
+
+		if (getsockopt(fd, SOL_SOCKET, optname, &tv, &len) < 0) {
+			if (errno == ENOTSOCK)
+				return 1;
+			die_errno("getsockopt failed to get socket option");
+		}
+		if (tv.tv_sec > 0 || tv.tv_usec > 0)
+			die("socket timed out");
+
+		return 1;
+	}
+
+	return 0;
+}
+
 /*
  * xread() is the same a read(), but it automatically restarts read()
  * operations with a recoverable error (EAGAIN and EINTR). xread()
@@ -119,7 +162,7 @@ ssize_t xread(int fd, void *buf, size_t len)
 	ssize_t nr;
 	while (1) {
 		nr = read(fd, buf, len);
-		if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
+		if (nr < 0 && io_continuable(fd, SO_RCVTIMEO))
 			continue;
 		return nr;
 	}
@@ -135,7 +178,7 @@ ssize_t xwrite(int fd, const void *buf, size_t len)
 	ssize_t nr;
 	while (1) {
 		nr = write(fd, buf, len);
-		if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
+		if (nr < 0 && io_continuable(fd, SO_SNDTIMEO))
 			continue;
 		return nr;
 	}
-- 
Eric Wong
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]