[SCM] Samba Shared Repository - branch master updated

Amitay Isaacs amitay at samba.org
Fri Jun 29 04:48:03 UTC 2018


The branch, master has been updated
       via  b6b1226 ctdb: Improve robust mutex test
      from  396f123 README.Coding: Fix link to Python coding style guide (PEP 8)

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit b6b1226d394445c21e01fdf3010642c85dbe85dd
Author: Carlos O'Donell <codonell at redhat.com>
Date:   Fri Jun 15 13:32:46 2018 +0200

    ctdb: Improve robust mutex test
    
    This avoids some of the undefined behaviour, like initializing the same mutex
    twice which happens when the low and high priority processes start (both
    do the initialization and that's dangerous.) Instead now we start an
    "init" process to start the shared memory segment, and then everything
    else just uses it without truncation or unlinking (same mutex).
    
    Signed-off-by: Carlos O'Donell <codonell at redhat.com>
    Reviewed-by: Andreas Schneider <asn at samba.org>
    Reviewed-by: Amitay Isaacs <amitay at gmail.com>
    
    Autobuild-User(master): Amitay Isaacs <amitay at samba.org>
    Autobuild-Date(master): Fri Jun 29 06:47:00 CEST 2018 on sn-devel-144

-----------------------------------------------------------------------

Summary of changes:
 ctdb/tests/src/test_mutex_raw.c | 483 +++++++++++++++++++++++++++-------------
 1 file changed, 333 insertions(+), 150 deletions(-)


Changeset truncated at 500 lines:

diff --git a/ctdb/tests/src/test_mutex_raw.c b/ctdb/tests/src/test_mutex_raw.c
index ab7aff9..926a525 100644
--- a/ctdb/tests/src/test_mutex_raw.c
+++ b/ctdb/tests/src/test_mutex_raw.c
@@ -1,251 +1,434 @@
 /*
-   Robust mutex test
-
-   Copyright (C) Amitay Isaacs  2016
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 3 of the License, or
-   (at your option) any later version.
-
-   This program 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 General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, see <http://www.gnu.org/licenses/>.
-*/
+ * Test the system robust mutex implementation
+ *
+ * Copyright (C) 2016 Amitay Isaacs
+ * Copyright (C) 2018 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
 
 /*
- * Run this test as follows:
+ * To run the test do the following:
+ *
+ * (a) Compile the test.
+ *
+ *     gcc -O2 -g3 -o test-robust-mutex test-robust-mutex.c -lpthread
  *
- * 1. Running all processes at normal priority
+ * (b) Start the "init" process.
  *
- *  $ while true ; do ./bin/test_mutex_raw /tmp/foo 10 0 ; done
+ *     ./test-robust-mutex /tmp/shared-mutex init
  *
- * 2. Running all processes at real-time priority
+ * (c) Start any number of "worker" instances.
  *
- *  # while true ; do ./bin/test_mutex_raw /tmp/foo 10 1 ; done
+ *     ./test-robust-mutex <Shared memory file> worker <#> <Priority>
  *
- * The test will block after few iterations.  At this time none of the 
- * child processes is holding the mutex.
+ *     <Shared memory file> e.g. /tmp/shared-mutex.
  *
- * To check which process is holding a lock:
+ *     <#> : Number of children processes.
  *
- *  $ ./bin/test_mutex_raw /tmp/foo debug
+ *     <Priority> : 0 - Normal, 1 - Realtime, 2 - Nice 20.
  *
- *  If no pid is printed, then no process is holding the mutex.
+ *    For example:
+ *
+ *     As non-root:
+ *
+ *     $ while true ; do ./test-robust-mutex /tmp/foo worker 10 0 ; done;
+ *
+ *     As root:
+ *
+ *     while true ; do ./test-robust-mutex /tmp/foo worker 10 1 ; done;
+ *
+ *    This creates 20 processes, 10 at normal priority and 10 at realtime
+ *    priority, all taking the lock, being killed and recovering the lock.
+ *
+ * If while runnig (c) the processes block, it might mean that a futex wakeup
+ * was lost, or that the handoff of EOWNERDEAD did not happen correctly. In
+ * either case you can debug the resulting mutex like this:
+ *
+ *     $ ./test-robust-mutex /tmp/shared-mutex debug
+ *
+ * This prints the PID of the process holding the mutex or nothing if
+ * the value was cleared by the kernel and now no process holds the mutex.
  */
 
-#include "replace.h"
-#include "system/filesys.h"
-#include "system/wait.h"
-#include "system/shmem.h"
-#include "system/threads.h"
-
-static void set_realtime(void)
-{
-	struct sched_param p;
-	int ret;
-
-	p.sched_priority = 1;
-
-	ret = sched_setscheduler(0, SCHED_FIFO, &p);
-	if (ret == -1) {
-		fprintf(stderr, "Failed to set scheduler to SCHED_FIFO\n");
-	}
-}
-
-static void high_priority(void)
-{
-	int ret;
-
-	ret = nice(-20);
-	if (ret == -1) {
-		fprintf(stderr, "Failed to set high priority\n");
-	}
-}
-
-static void run_child(const char *filename)
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/wait.h>
+
+/* Define DEBUG to 1 to enable verbose debugging.  */
+#define DEBUG 0
+
+/* Implement the worker.  The worker has to do the following things:
+
+   * Succeed at locking the mutex, including possible recovery.
+   * Kill itself.
+
+   Other workers are attempting exactly the same thing in order to
+   test the loss and recovery of the robust mutex.  */
+static void worker (const char *filename)
 {
 	pthread_mutex_t *mutex;
 	void *addr;
 	int ret, fd;
 
+	/* Open the file and map the shared robust mutex.  */
 	fd = open(filename, O_RDWR, 0600);
 	if (fd == -1) {
-		exit(1);
+		perror ("FAIL: open");
+		exit(EXIT_FAILURE);
 	}
 
-	addr = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ|PROT_WRITE,
-		    MAP_SHARED|MAP_FILE, fd, 0);
+	addr = mmap(NULL,
+		    sizeof(pthread_mutex_t),
+		    PROT_READ|PROT_WRITE,
+		    MAP_SHARED|MAP_FILE,
+		    fd,
+		    0);
 	if (addr == NULL) {
-		exit(2);
+		perror ("FAIL: mmap");
+		exit(EXIT_FAILURE);
 	}
 
 	mutex = (pthread_mutex_t *)addr;
 
-again:
-	ret = pthread_mutex_lock(mutex);
-	if (ret == EOWNERDEAD) {
-		ret = pthread_mutex_consistent(mutex);
-	} else if (ret == EAGAIN) {
-		goto again;
-	}
-	if (ret != 0) {
-		fprintf(stderr, "pid %u lock failed, ret=%d\n", getpid(), ret);
-		exit(3);
-	}
+	/* Every process will lock once, and die once.  */
+	printf("INFO: pid %u locking\n", getpid());
+	do {
+		ret = pthread_mutex_lock(mutex);
+
+#if DEBUG
+		fprintf(stderr,
+			"DEBUG: pid %u lock attempt, ret=%d\n",
+			getpid(),
+			ret);
+#endif
+
+		if (ret == EOWNERDEAD) {
+			int rc;
+
+			rc = pthread_mutex_consistent(mutex);
+			if (rc == 0) {
+				pthread_mutex_unlock(mutex);
+			} else {
+				fprintf(stderr,
+					"FAIL: pthread_mutex_consistent "
+					"failed\n");
+				exit(EXIT_FAILURE);
+			}
+#if DEBUG
+			fprintf(stderr,
+				"DEBUG: pid %u recovery lock attempt, ret=%d\n",
+				getpid(),
+				ret);
+#endif
+			/* Will loop and try to lock again.  */
+		}
 
-	fprintf(stderr, "pid %u locked\n", getpid());
+	} while (ret != 0);
+
+	printf ("INFO: pid %u locked, now killing\n", getpid());
 	kill(getpid(), SIGKILL);
 }
 
+/* One of three priority modes.  */
 #define PRIO_NORMAL	0
 #define PRIO_REALTIME	1
 #define PRIO_NICE_20	2
 
+/* One of three operation modes.  */
+#define MODE_INIT	0
+#define MODE_WORKER	1
+#define MODE_DEBUG	2
+
+/* Print usage information and exit.  */
+static void usage (const char *name)
+{
+	fprintf(stderr,
+		"Usage: %s <file> [init|worker|debug] [#] [0|1|2]\n",
+		name);
+	exit(EXIT_FAILURE);
+}
+
+/* Set the process priority.  */
+static void set_priority (int priority)
+{
+	struct sched_param p;
+	int ret;
+
+	switch (priority) {
+	case PRIO_REALTIME:
+		p.sched_priority = 1;
+		ret = sched_setscheduler(0, SCHED_FIFO, &p);
+		if (ret == -1)
+			perror("FAIL: sched_setscheduler");
+		break;
+
+	case PRIO_NICE_20:
+		ret = nice(-20);
+		if (ret == -1)
+			perror("FAIL: nice");
+		break;
+
+	case PRIO_NORMAL:
+	default:
+		/* Normal priority is the default.  */
+		break;
+	}
+}
+
 int main(int argc, const char **argv)
 {
-	pthread_mutexattr_t ma;
+	int i, fd, ret, num_children, mode = -1, priority = PRIO_NORMAL;
+	const char *mode_str;
+	const char *file;
+	char *addr;
 	pthread_mutex_t *mutex;
-	int fd, ret, i;
+	pthread_mutexattr_t mattr;
 	pid_t pid;
-	void *addr;
-	int num_children;
-	int priority = PRIO_NORMAL;
-
-	if (argc < 3 || argc > 4) {
-		fprintf(stderr, "Usage: %s <file> <n> [0|1|2]\n", argv[0]);
-		fprintf(stderr, "       %s <file> debug\n", argv[0]);
-		exit(1);
-	}
 
-	if (argc == 4) {
-		priority = atoi(argv[3]);
+	/* One of three modes, init, worker, or debug.  */
+	if (argc < 3 || argc > 5)
+		usage (argv[0]);
+
+	/*
+	 * The shared memory file.  Care should be taken here because if glibc
+	 * is upgraded between runs the internals of the robust mutex could
+	 * change. See this blog post about the dangers:
+	 * https://developers.redhat.com/blog/2017/03/13/cc-library-upgrades-and-opaque-data-types-in-process-shared-memory/
+	 * and how to avoid problems inherent in this.
+	 */
+	file = argv[1];
+
+	/* Set the mode.  */
+	mode_str = argv[2];
+	if (strcmp ("init", mode_str) == 0) {
+		mode = MODE_INIT;
+	} else if (strcmp ("worker", mode_str) == 0) {
+		mode = MODE_WORKER;
+	} else if (strcmp ("debug", mode_str) == 0) {
+		mode = MODE_DEBUG;
+	} else {
+		usage (argv[0]);
 	}
 
-	if (priority == PRIO_REALTIME) {
-		set_realtime();
-	} else if (priority == PRIO_NICE_20) {
-		high_priority();
+	/* This is "worker" mode, so set the priority.  */
+	if (mode == MODE_WORKER) {
+		priority = atoi(argv[4]);
+		set_priority(priority);
 	}
 
+	/* All modes open the file.  */
 	fd = open(argv[1], O_CREAT|O_RDWR, 0600);
 	if (fd == -1) {
-		fprintf(stderr, "open failed\n");
-		exit(1);
+		perror("FAIL: open");
+		exit(EXIT_FAILURE);
 	}
 
 	ret = lseek(fd, 0, SEEK_SET);
 	if (ret != 0) {
-		fprintf(stderr, "lseek failed\n");
-		exit(1);
+		perror("FAIL: lseek");
+		exit(EXIT_FAILURE);
 	}
 
-	ret = ftruncate(fd, sizeof(pthread_mutex_t));
-	if (ret != 0) {
-		fprintf(stderr, "ftruncate failed\n");
-		exit(1);
+	/* Truncate the file backing the mutex only in the init phase.  */
+	if (mode == MODE_INIT) {
+		ret = ftruncate(fd, sizeof(pthread_mutex_t));
+		if (ret != 0) {
+			perror("FAIL: ftruncate");
+			exit(EXIT_FAILURE);
+		}
 	}
 
-	addr = mmap(NULL, sizeof(pthread_mutex_t), PROT_READ|PROT_WRITE,
-		    MAP_SHARED|MAP_FILE, fd, 0);
+	/* Map the robust mutex.  */
+	addr = mmap(NULL,
+		    sizeof(pthread_mutex_t),
+		    PROT_READ|PROT_WRITE,
+		    MAP_SHARED|MAP_FILE,
+		    fd,
+		    0);
 	if (addr == NULL) {
-		fprintf(stderr, "mmap failed\n");
-		exit(1);
+		perror("FAIL: mmap");
+		exit(EXIT_FAILURE);
 	}
 
-	mutex = (pthread_mutex_t *)addr;
+	mutex = (pthread_mutex_t *)(void *)addr;
 
-	if (strcmp(argv[2], "debug") == 0) {
+	/*
+	 * In the debug mode we try to recover the mutex and print it.
+	 * WARNING: All other processes should be stuck, otherwise they may
+	 * change the value of the lock between trylock and the printing after
+	 * EBUSY.
+	 */
+	if (mode == MODE_DEBUG) {
 		ret = pthread_mutex_trylock(mutex);
 		if (ret == EOWNERDEAD) {
 			ret = pthread_mutex_consistent(mutex);
 			if (ret == 0) {
 				pthread_mutex_unlock(mutex);
+			} else {
+				fprintf(stderr,
+					"FAIL: pthread_mutex_consistent "
+					"failed\n");
+				exit (EXIT_FAILURE);
 			}
 		} else if (ret == EBUSY) {
-			printf("pid=%u\n", mutex->__data.__owner);
+			printf("INFO: pid=%u\n", mutex->__data.__owner);
 		} else if (ret == 0) {
 			pthread_mutex_unlock(mutex);
 		}
-		exit(0);
+		exit(EXIT_SUCCESS);
 	}
 
-	ret = pthread_mutexattr_init(&ma);
-	if (ret != 0) {
-		fprintf(stderr, "pthread_mutexattr_init failed\n");
-		exit(1);
-	}
+	/*
+	 * Only the initializing process does initialization because it is
+	 * undefined behaviour to re-initialize an already initialized mutex
+	 * that was not destroyed.
+	 */
+	if (mode == MODE_INIT) {
+
+		ret = pthread_mutexattr_init(&mattr);
+		if (ret != 0) {
+			fprintf(stderr,
+				"FAIL: pthread_mutexattr_init failed\n");
+			exit(EXIT_FAILURE);
+		}
 
-	ret = pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK);
-	if (ret != 0) {
-		fprintf(stderr, "pthread_mutexattr_settype failed\n");
-		exit(1);
-	}
+		ret = pthread_mutexattr_settype(&mattr,
+						PTHREAD_MUTEX_ERRORCHECK);
+		if (ret != 0) {
+			fprintf(stderr,
+				"FAIL: pthread_mutexattr_settype failed\n");
+			exit(EXIT_FAILURE);
+		}
 
-	ret = pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED);
-	if (ret != 0) {
-		fprintf(stderr, "pthread_mutexattr_setpshared failed\n");
-		exit(1);
-	}
+		ret = pthread_mutexattr_setpshared(&mattr,
+						   PTHREAD_PROCESS_SHARED);
+		if (ret != 0) {
+			fprintf(stderr,
+				"FAIL: pthread_mutexattr_setpshared failed\n");
+			exit(EXIT_FAILURE);
+		}
 
-	ret = pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST);
-	if (ret != 0) {
-		fprintf(stderr, "pthread_mutexattr_setrobust failed\n");
-		exit(1);
-	}
+		ret = pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
+		if (ret != 0) {
+			fprintf(stderr,
+				"FAIL: pthread_mutexattr_setrobust failed\n");
+			exit(EXIT_FAILURE);
+		}
 
-	ret = pthread_mutex_init(mutex, &ma);
-	if (ret != 0) {
-		fprintf(stderr, "pthread_mutex_init failed\n");
-		exit(1);
-	}
+		ret = pthread_mutex_init(mutex, &mattr);
+		if (ret != 0) {
+			fprintf(stderr, "FAIL: pthread_mutex_init failed\n");
+			exit(EXIT_FAILURE);
+		}
 
-	ret = pthread_mutex_lock(mutex);
-	if (ret != 0) {
-		fprintf(stderr, "pthread_mutex_lock failed\n");
-		exit(1);
+		printf ("INFO: init: Mutex initialization complete.\n");
+		/* Never exit.  */
+		for (;;)
+			sleep (1);
 	}
 
+	/* Acquire the mutext for the first time. Might be dead.
+	   Might also be concurrent with the high-priority threads.  */
+	fprintf(stderr,
+		"INFO: parent: Acquiring mutex (pid = %d).\n",
+		getpid());
+	do {
+		ret = pthread_mutex_lock(mutex);
+
+		/* Not consistent? Try to make it so.  */
+		if (ret == EOWNERDEAD) {


-- 
Samba Shared Repository



More information about the samba-cvs mailing list