[PATCH 3 of 3 V2] chg: hold a lock file before connected to server

Jun Wu quark at fb.com
Mon Feb 22 12:59:09 EST 2016


# HG changeset patch
# User Jun Wu <quark at fb.com>
# Date 1455620932 0
#      Tue Feb 16 11:08:52 2016 +0000
# Node ID 43e380f60dd4373090fb94497761ebfdc181ca54
# Parent  118b386f47cb6e6aa4f231c9ae22bc2e155e7b99
chg: hold a lock file before connected to server

This is a part of the one server per config series. In multiple-server setup,
multiple clients may try to start different servers (on demand) at the same
time. The old lock will not guarntee a client to connect to the server it
just started, and is not crash friendly.

This patch addressed above issues by using flock and does not release the lock
until the client actually connects to the server or times out.

diff --git a/contrib/chg/chg.c b/contrib/chg/chg.c
--- a/contrib/chg/chg.c
+++ b/contrib/chg/chg.c
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/un.h>
@@ -34,10 +35,12 @@
 	char pidfile[UNIX_PATH_MAX];
 	size_t argsize;
 	const char **args;
+	int lockfd;
 };
 
 static void initcmdserveropts(struct cmdserveropts *opts) {
 	memset(opts, 0, sizeof(struct cmdserveropts));
+	opts->lockfd = -1;
 }
 
 static void freecmdserveropts(struct cmdserveropts *opts) {
@@ -158,21 +161,33 @@
 }
 
 /*
- * Make lock file that indicates cmdserver process is about to start. Created
- * lock file will be deleted by server. (0: success, -1: lock exists)
+ * Acquire a file lock that indicates a client is trying to start and connect
+ * to a server, before executing a command. The lock is released upon exit or
+ * explicitly unlock. Will block if the lock is held by another process.
  */
-static int lockcmdserver(const struct cmdserveropts *opts)
+static void lockcmdserver(struct cmdserveropts *opts)
 {
-	int r;
-	char info[32];
-	r = snprintf(info, sizeof(info), "%d", getpid());
-	if (r < 0 || (size_t)r >= sizeof(info))
-		abortmsg("failed to format lock info");
-	r = symlink(info, opts->lockfile);
-	if (r < 0 && errno != EEXIST)
-		abortmsg("failed to make lock %s (errno = %d)",
-			 opts->lockfile, errno);
-	return r;
+	if (opts->lockfd == -1) {
+		opts->lockfd = open(opts->lockfile, O_RDWR | O_CREAT | O_NOFOLLOW, 0600);
+		if (opts->lockfd == -1)
+			abortmsg("cannot create lock file %s", opts->lockfile);
+	}
+	int r = flock(opts->lockfd, LOCK_EX);
+	if (r == -1)
+		abortmsg("cannot acquire lock");
+}
+
+/*
+ * Release the file lock held by calling lockcmdserver. Will do nothing if
+ * lockcmdserver is not called.
+ */
+static void unlockcmdserver(struct cmdserveropts *opts)
+{
+	if (opts->lockfd == -1)
+		return;
+	flock(opts->lockfd, LOCK_UN);
+	close(opts->lockfd);
+	opts->lockfd = -1;
 }
 
 static void execcmdserver(const struct cmdserveropts *opts)
@@ -189,7 +204,7 @@
 		"--cwd", "/",
 		"--cmdserver", "chgunix",
 		"--address", opts->sockname,
-		"--daemon-postexec", opts->lockfile,
+		"--daemon-postexec", "none",
 		"--pid-file", opts->pidfile,
 		"--config", "extensions.chgserver=",
 		/* wrap root ui so that it can be disabled/enabled by config */
@@ -208,28 +223,20 @@
 	free(argv);
 }
 
-/*
- * Sleep until lock file is deleted, i.e. cmdserver process starts listening.
- * If pid is given, it also checks if the child process fails to start.
- */
-static void waitcmdserver(const struct cmdserveropts *opts, pid_t pid)
+/* Retry until we can connect to the server. Give up after some time. */
+static hgclient_t *retryconnectcmdserver(struct cmdserveropts *opts, pid_t pid)
 {
 	static const struct timespec sleepreq = {0, 10 * 1000000};
 	int pst = 0;
 
 	for (unsigned int i = 0; i < 10 * 100; i++) {
-		int r;
-		struct stat lst;
-
-		r = lstat(opts->lockfile, &lst);
-		if (r < 0 && errno == ENOENT)
-			return;  /* lock file deleted by server */
-		if (r < 0)
-			goto cleanup;
+		hgclient_t *hgc = hgc_open(opts->sockname);
+		if (hgc)
+			return hgc;
 
 		if (pid > 0) {
 			/* collect zombie if child process fails to start */
-			r = waitpid(pid, &pst, WNOHANG);
+			int r = waitpid(pid, &pst, WNOHANG);
 			if (r != 0)
 				goto cleanup;
 		}
@@ -237,13 +244,10 @@
 		nanosleep(&sleepreq, NULL);
 	}
 
-	abortmsg("timed out waiting for cmdserver %s", opts->lockfile);
-	return;
+	abortmsg("timed out waiting for cmdserver %s", opts->sockname);
+	return NULL;
 
 cleanup:
-	if (pid > 0)
-		/* lockfile should be made by this process */
-		unlink(opts->lockfile);
 	if (WIFEXITED(pst)) {
 		abortmsg("cmdserver exited with status %d", WEXITSTATUS(pst));
 	} else if (WIFSIGNALED(pst)) {
@@ -251,26 +255,32 @@
 	} else {
 		abortmsg("error white waiting cmdserver");
 	}
+	return NULL;
 }
 
-/* Spawn new background cmdserver */
-static void startcmdserver(const struct cmdserveropts *opts)
+/* Connect to a cmdserver. Will start a new server on demand. */
+static hgclient_t *connectcmdserver(struct cmdserveropts *opts)
 {
-	debugmsg("start cmdserver at %s", opts->sockname);
+	hgclient_t *hgc = hgc_open(opts->sockname);
+	if (hgc)
+		return hgc;
 
-	if (lockcmdserver(opts) < 0) {
-		debugmsg("lock file exists, waiting...");
-		waitcmdserver(opts, 0);
-		return;
+	lockcmdserver(opts);
+	hgc = hgc_open(opts->sockname);
+	if (hgc) {
+		unlockcmdserver(opts);
+		debugmsg("cmdserver is started by another process");
+		return hgc;
 	}
 
-	/* remove dead cmdserver socket if any */
-	unlink(opts->sockname);
+	debugmsg("start cmdserver at %s", opts->sockname);
 
 	pid_t pid = fork();
 	if (pid < 0)
 		abortmsg("failed to fork cmdserver process");
 	if (pid == 0) {
+		/* do not leak lockfd to hg */
+		close(opts->lockfd);
 		/* bypass uisetup() of pager extension */
 		int nullfd = open("/dev/null", O_WRONLY);
 		if (nullfd >= 0) {
@@ -279,8 +289,11 @@
 		}
 		execcmdserver(opts);
 	} else {
-		waitcmdserver(opts, pid);
+		hgc = retryconnectcmdserver(opts, pid);
 	}
+
+	unlockcmdserver(opts);
+	return hgc;
 }
 
 static void killcmdserver(const struct cmdserveropts *opts, int sig)
@@ -448,11 +461,7 @@
 		}
 	}
 
-	hgclient_t *hgc = hgc_open(opts.sockname);
-	if (!hgc) {
-		startcmdserver(&opts);
-		hgc = hgc_open(opts.sockname);
-	}
+	hgclient_t *hgc = connectcmdserver(&opts);
 	if (!hgc)
 		abortmsg("cannot open hg client");
 


More information about the Mercurial-devel mailing list