Mercurial > hg
changeset 28196:87de4a22e8c2
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 guarantee 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.
author | Jun Wu <quark@fb.com> |
---|---|
date | Tue, 16 Feb 2016 11:08:52 +0000 |
parents | 213c8cf02c49 |
children | 2ada62388bb1 |
files | contrib/chg/chg.c |
diffstat | 1 files changed, 57 insertions(+), 48 deletions(-) [+] |
line wrap: on
line diff
--- a/contrib/chg/chg.c Mon Feb 22 17:30:02 2016 +0000 +++ b/contrib/chg/chg.c Tue Feb 16 11:08:52 2016 +0000 @@ -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 + * explicit 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");