--- /dev/null
+/* Copyright Rusty Russell 2013, IBM Corporation
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#define HAVE_CLOCK_GETTIME_IN_LIBRT 1
+#define HAVE_STRUCT_TIMESPEC 1
+#include <err.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <assert.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+/* Taken from ccan/time: Licensed under BSD-MIT - see LICENSE file for details */
+#if HAVE_STRUCT_TIMESPEC
+#include <time.h>
+#else
+struct timespec {
+ time_t tv_sec; /* seconds */
+ long tv_nsec; /* nanoseconds */
+};
+#endif
+
+#if !HAVE_CLOCK_GETTIME && !HAVE_CLOCK_GETTIME_IN_LIBRT
+#include <sys/time.h>
+
+struct timespec time_now(void)
+{
+ struct timeval now;
+ struct timespec ret;
+ gettimeofday(&now, NULL);
+ ret.tv_sec = now.tv_sec;
+ ret.tv_nsec = now.tv_usec * 1000;
+ return ret;
+}
+#else
+#include <time.h>
+struct timespec time_now(void)
+{
+ struct timespec ret;
+ clock_gettime(CLOCK_REALTIME, &ret);
+ return ret;
+}
+#endif /* HAVE_CLOCK_GETTIME || HAVE_CLOCK_GETTIME_IN_LIBRT */
+
+static struct timespec time_sub(struct timespec recent,
+ struct timespec old)
+{
+ struct timespec diff;
+
+ diff.tv_sec = recent.tv_sec - old.tv_sec;
+ if (old.tv_nsec > recent.tv_nsec) {
+ diff.tv_sec--;
+ diff.tv_nsec = 1000000000 + recent.tv_nsec - old.tv_nsec;
+ } else
+ diff.tv_nsec = recent.tv_nsec - old.tv_nsec;
+
+ return diff;
+}
+
+/* Taken from TDB: LGPLv3 */
+static int fcntl_lock(int fd, int rw, off_t off, off_t len, bool waitflag)
+{
+ struct flock fl;
+
+ fl.l_type = rw;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = off;
+ fl.l_len = len;
+ fl.l_pid = 0;
+
+ if (waitflag)
+ return fcntl(fd, F_SETLKW, &fl);
+ else
+ return fcntl(fd, F_SETLK, &fl);
+}
+
+static int fcntl_unlock(int fd, int rw, off_t off, off_t len)
+{
+ struct flock fl;
+ fl.l_type = F_UNLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = off;
+ fl.l_len = len;
+ fl.l_pid = 0;
+
+ return fcntl(fd, F_SETLKW, &fl);
+}
+
+int main(int argc, char *argv[])
+{
+ int num = 5000, i;
+ const char *lockfile = argv[1];
+ char *fill;
+ int lockfd, to_lockers[2], from_lockers[2];
+ struct timespec start, diff;
+
+ if (argc > 2) {
+ num = atoi(argv[2]);
+ argv++;
+ argc--;
+ }
+ if (argc != 2)
+ errx(1, "Usage: lock-stampede <filename> [num]");
+
+ pipe(to_lockers);
+ pipe(from_lockers);
+ lockfd = open(lockfile, O_RDWR);
+ if (lockfd < 0)
+ err(1, "Opening %s", lockfile);
+
+ start = time_now();
+ for (i = 0; i < num; i++) {
+ char c;
+
+ switch (fork()) {
+ case 0:
+ close(from_lockers[0]);
+ close(to_lockers[1]);
+ /* Parent dies? */
+ if (read(to_lockers[0], &c, 1) != 1)
+ exit(0);
+ if (fcntl_lock(lockfd, F_WRLCK, i, 1, true) != 0)
+ err(1, "Locking %s", lockfile);
+ /* Tell parent we got it! */
+ if (write(from_lockers[1], &c, 1) != 1)
+ err(1, "Writing to parent");
+ /* If we exit, we'd release it, so wait. */
+ read(to_lockers[0], &c, 1);
+ exit(0);
+ case -1:
+ err(1, "fork failed");
+ }
+ }
+
+ close(to_lockers[0]);
+ close(from_lockers[1]);
+
+ diff = time_sub(time_now(), start);
+ printf("Children set up in %lu.%09u\n",
+ (long)diff.tv_sec, (int)diff.tv_nsec);
+
+ if (fcntl_lock(lockfd, F_WRLCK, 0, num, true) != 0)
+ err(1, "Locking %u bytes in %s", num, lockfile);
+
+ fill = calloc(num, 1);
+
+ /* OK, now wake all the kids: they block on lock. */
+ i = 0;
+ do {
+ int ret = write(to_lockers[1], fill + i, num - i);
+
+ if (ret < 0)
+ err(1, "writing to wake up locker children");
+ i += ret;
+ } while (i < num);
+
+ sleep(1);
+
+ start = time_now();
+ fcntl_unlock(lockfd, F_WRLCK, 0, num);
+ for (i = 0; i < num; i++)
+ if (read(from_lockers[0], fill, 1) != 1)
+ err(1, "Reading from locker children");
+ diff = time_sub(time_now(), start);
+ printf("Children got locks in %lu.%09u\n",
+ (long)diff.tv_sec, (int)diff.tv_nsec);
+
+ /* Children will now exit. */
+ exit(0);
+}