[PATCH] talloc with threads tutorial and tests

Jeremy Allison jra at samba.org
Mon Mar 16 17:13:15 MDT 2015


On Mon, Mar 16, 2015 at 05:04:33PM -0400, Simo wrote:
> On Mon, 2015-03-16 at 12:49 -0700, Jeremy Allison wrote:
> > 
> > Also, I'm eventually planning to tie this in with
> > a tevent+threads tutorial, showing how to have each
> > thread run a seprate event loop, and then synchronise
> > talloced memory passing between them when they need
> > to communicate. That's a perfect place to use the
> > sync-pipe example as it ties into the thread-local
> > tevent code.
> 
> Excellent, and you get a reviewed-by too.
> 
> Simo.

Sigh. It turns out that to actually get
lib/talloc/testsuite.c to link correctly
with -lpthread and run standalone inside
the autobuild test environment a change
to lib/talloc/wscript is also needed (this
of course wasn't needed on my local box,
where all make tests passed :-).

diff --git a/lib/talloc/wscript b/lib/talloc/wscript
index 97c52c3..613b0c5 100644
--- a/lib/talloc/wscript
+++ b/lib/talloc/wscript
@@ -80,6 +80,10 @@ def build(bld):
         bld.env.TALLOC_VERSION = VERSION
         private_library = False
 
+        testsuite_deps = 'talloc'
+        if bld.CONFIG_SET('HAVE_PTHREAD'):
+            testsuite_deps += ' pthread'
+
         # should we also install the symlink to libtalloc1.so here?
         bld.SAMBA_LIBRARY('talloc-compat1-%s' % (VERSION),
                           'compat/talloc_compat1.c',
@@ -91,7 +95,7 @@ def build(bld):
 
         bld.SAMBA_BINARY('talloc_testsuite',
                          'testsuite_main.c testsuite.c',
-                         deps='talloc',
+                         testsuite_deps,
                          install=False)
 
     else:

Here is the complete patchset again with this change
included. Can I get a re-review so I can fight
with autobuild some more ? :-).

Cheers,

	Jeremy.
-------------- next part --------------
From 8b0954514687a4c2c2487c1a5afa16f0a242b130 Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Mon, 16 Mar 2015 12:17:40 -0700
Subject: [PATCH 1/2] lib: docs: talloc: Add a threads tutorial and samples
 showing how to use talloc with threads.

Signed-off-by: Jeremy Allison <jra at samba.org>
Reviewed-by: Simo <simo at samba.org>
---
 lib/talloc/doc/mainpage.dox              |   3 +-
 lib/talloc/doc/tutorial_introduction.dox |   4 +-
 lib/talloc/doc/tutorial_threads.dox      | 203 +++++++++++++++++++++++++++++++
 lib/talloc/talloc_guide.txt              |   3 +-
 4 files changed, 210 insertions(+), 3 deletions(-)
 create mode 100644 lib/talloc/doc/tutorial_threads.dox

diff --git a/lib/talloc/doc/mainpage.dox b/lib/talloc/doc/mainpage.dox
index 3b56898..ece6ccb 100644
--- a/lib/talloc/doc/mainpage.dox
+++ b/lib/talloc/doc/mainpage.dox
@@ -102,7 +102,8 @@
  *   - when using talloc_enable_leak_report(), giving directly NULL as a parent
  *     context implicitly refers to a hidden "null context" global variable, so
  *     this should not be used in a multi-threaded environment without proper
- *     synchronization.
+ *     synchronization. In threaded code turn off null tracking using
+ *     talloc_disable_null_tracking().
  *   - the context returned by talloc_autofree_context() is also global so
  *     shouldn't be used by several threads simultaneously without
  *     synchronization.
diff --git a/lib/talloc/doc/tutorial_introduction.dox b/lib/talloc/doc/tutorial_introduction.dox
index 02777b9..418c38b 100644
--- a/lib/talloc/doc/tutorial_introduction.dox
+++ b/lib/talloc/doc/tutorial_introduction.dox
@@ -40,4 +40,6 @@ recursively frees all of its descendants as well.
 
 @subpage libtalloc_bestpractices
 
-*/
\ No newline at end of file
+ at subpage libtalloc_threads
+
+*/
diff --git a/lib/talloc/doc/tutorial_threads.dox b/lib/talloc/doc/tutorial_threads.dox
new file mode 100644
index 0000000..696f653
--- /dev/null
+++ b/lib/talloc/doc/tutorial_threads.dox
@@ -0,0 +1,203 @@
+/**
+ at page libtalloc_stealing Chapter 8: Using threads with talloc
+
+ at section Talloc and thread safety
+
+The talloc library is not internally thread-safe, in that accesses
+to variables on a talloc context are not controlled by mutexes or
+other thread-safe primitives.
+
+However, so long as talloc_disable_null_tracking() is called from
+the main thread to disable global variable access within talloc,
+then each thread can safely use its own top level talloc context
+allocated off the NULL context.
+
+For example:
+
+ at code
+static void *thread_fn(void *arg)
+{
+	const char *ctx_name = (const char *)arg;
+        /*
+         * Create a new top level talloc hierarchy in
+         * this thread.
+         */
+	void *top_ctx = talloc_named_const(NULL, 0, "top");
+	if (top_ctx == NULL) {
+		return NULL;
+	}
+	sub_ctx = talloc_named_const(top_ctx, 100, ctx_name);
+	if (sub_ctx == NULL) {
+		return NULL;
+	}
+
+	/*
+	 * Do more processing/talloc calls on top_ctx
+	 * and its children.
+	 */
+	......
+
+	talloc_free(top_ctx);
+	return value;
+}
+ at endcode
+
+is a perfectly safe use of talloc within a thread.
+
+The problem comes when one thread wishes to move some
+memory allocated on its local top level talloc context
+to another thread. Care must be taken to add data access
+exclusion to prevent memory corruption. One method would
+be to lock a mutex before any talloc call on each thread,
+but this would push the burden of total talloc thread-safety
+on the poor user of the library.
+
+A much easier way to transfer talloced memory between
+threads is by the use of an intermediate, mutex locked,
+intermediate variable.
+
+An example of this is below - taken from test code inside
+the talloc testsuite.
+
+The main thread creates 1000 sub-threads, and then accepts
+the transfer of some thread-talloc'ed memory onto its top
+level context from each thread in turn.
+
+A pthread mutex and condition variable are used to
+synchronize the transfer via the intermediate_ptr
+variable.
+
+ at code
+/* Required sync variables. */
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
+
+/* Intermediate talloc pointer for transfer. */
+static void *intermediate_ptr;
+
+/* Subthread. */
+static void *thread_fn(void *arg)
+{
+	int ret;
+	const char *ctx_name = (const char *)arg;
+	void *sub_ctx = NULL;
+	/*
+	 * Do stuff that creates a new talloc hierarchy in
+	 * this thread.
+	 */
+	void *top_ctx = talloc_named_const(NULL, 0, "top");
+	if (top_ctx == NULL) {
+		return NULL;
+	}
+	sub_ctx = talloc_named_const(top_ctx, 100, ctx_name);
+	if (sub_ctx == NULL) {
+		return NULL;
+	}
+
+	/*
+	 * Now transfer a pointer from our hierarchy
+	 * onto the intermediate ptr.
+	 */
+	ret = pthread_mutex_lock(&mtx);
+	if (ret != 0) {
+		talloc_free(top_ctx);
+		return NULL;
+	}
+
+	/* Wait for intermediate_ptr to be free. */
+	while (intermediate_ptr != NULL) {
+		ret = pthread_cond_wait(&condvar, &mtx);
+		if (ret != 0) {
+			talloc_free(top_ctx);
+			return NULL;
+		}
+	}
+
+	/* and move our memory onto it from our toplevel hierarchy. */
+	intermediate_ptr = talloc_move(NULL, &sub_ctx);
+
+	/* Tell the main thread it's ready for pickup. */
+	pthread_cond_broadcast(&condvar);
+	pthread_mutex_unlock(&mtx);
+
+	talloc_free(top_ctx);
+	return NULL;
+}
+
+/* Main thread. */
+
+#define NUM_THREADS 1000
+
+static bool test_pthread_talloc_passing(void)
+{
+	int i;
+	int ret;
+	char str_array[NUM_THREADS][20];
+	pthread_t thread_id;
+	void *mem_ctx;
+
+	/*
+	 * Important ! Null tracking breaks threaded talloc.
+	 * It *must* be turned off.
+	 */
+	talloc_disable_null_tracking();
+
+	/* Main thread toplevel context. */
+	mem_ctx = talloc_named_const(NULL, 0, "toplevel");
+	if (mem_ctx == NULL) {
+		return false;
+	}
+
+	/*
+	 * Spin off NUM_THREADS threads.
+	 * They will use their own toplevel contexts.
+	 */
+	for (i = 0; i < NUM_THREADS; i++) {
+		(void)snprintf(str_array[i],
+				20,
+				"thread:%d",
+				i);
+		if (str_array[i] == NULL) {
+			return false;
+		}
+		ret = pthread_create(&thread_id,
+				NULL,
+				thread_fn,
+				str_array[i]);
+		if (ret != 0) {
+			return false;
+		}
+	}
+
+	/* Now wait for NUM_THREADS transfers of the talloc'ed memory. */
+	for (i = 0; i < NUM_THREADS; i++) {
+		ret = pthread_mutex_lock(&mtx);
+		if (ret != 0) {
+			talloc_free(mem_ctx);
+			return false;
+		}
+
+		/* Wait for intermediate_ptr to have our data. */
+		while (intermediate_ptr == NULL) {
+			ret = pthread_cond_wait(&condvar, &mtx);
+			if (ret != 0) {
+				talloc_free(mem_ctx);
+				return false;
+			}
+		}
+
+		/* and move it onto our toplevel hierarchy. */
+		(void)talloc_move(mem_ctx, &intermediate_ptr);
+
+		/* Tell the sub-threads we're ready for another. */
+		pthread_cond_broadcast(&condvar);
+		pthread_mutex_unlock(&mtx);
+	}
+
+	/* Dump the hierarchy. */
+	talloc_report(mem_ctx, stdout);
+	talloc_free(mem_ctx);
+	return true;
+}
+ at endcode
+*/
diff --git a/lib/talloc/talloc_guide.txt b/lib/talloc/talloc_guide.txt
index 16afc9b..95f7f29 100644
--- a/lib/talloc/talloc_guide.txt
+++ b/lib/talloc/talloc_guide.txt
@@ -69,7 +69,8 @@ order to be safe. In particular:
 - when using talloc_enable_leak_report(), giving directly NULL as a  
 parent context implicitly refers to a hidden "null context" global  
 variable, so this should not be used in a multi-threaded environment  
-without proper synchronization ;
+without proper synchronization. In threaded code turn off null tracking using
+talloc_disable_null_tracking(). ;
 - the context returned by talloc_autofree_context() is also global so  
 shouldn't be used by several threads simultaneously without  
 synchronization.
-- 
2.2.0.rc0.207.ga3a616c


From 8ac2e1c9ef45a9108aa7d977238e01ed940eceaf Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra at samba.org>
Date: Mon, 16 Mar 2015 12:18:17 -0700
Subject: [PATCH 2/2] lib: talloc: tests - add test_pthread_talloc_passing()
 testing talloc in a pthread environment.

Signed-off-by: Jeremy Allison <jra at samba.org>
Reviewed-by: Simo <simo at samba.org>
---
 lib/talloc/testsuite.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++++
 lib/talloc/wscript     |   6 +-
 2 files changed, 150 insertions(+), 1 deletion(-)

diff --git a/lib/talloc/testsuite.c b/lib/talloc/testsuite.c
index eb3e13d..3011453 100644
--- a/lib/talloc/testsuite.c
+++ b/lib/talloc/testsuite.c
@@ -27,6 +27,10 @@
 #include "system/time.h"
 #include <talloc.h>
 
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+
 #include "talloc_testsuite.h"
 
 static struct timeval timeval_current(void)
@@ -1701,6 +1705,143 @@ static bool test_memlimit(void)
 	return true;
 }
 
+#ifdef HAVE_PTHREAD
+
+#define NUM_THREADS 1000
+
+/* Sync variables. */
+static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
+static void *intermediate_ptr;
+
+/* Subthread. */
+static void *thread_fn(void *arg)
+{
+	int ret;
+	const char *ctx_name = (const char *)arg;
+	void *sub_ctx = NULL;
+	/*
+	 * Do stuff that creates a new talloc hierarchy in
+	 * this thread.
+	 */
+	void *top_ctx = talloc_named_const(NULL, 0, "top");
+	if (top_ctx == NULL) {
+		return NULL;
+	}
+	sub_ctx = talloc_named_const(top_ctx, 100, ctx_name);
+	if (sub_ctx == NULL) {
+		return NULL;
+	}
+
+	/*
+	 * Now transfer a pointer from our hierarchy
+	 * onto the intermediate ptr.
+	 */
+	ret = pthread_mutex_lock(&mtx);
+	if (ret != 0) {
+		talloc_free(top_ctx);
+		return NULL;
+	}
+	/* Wait for intermediate_ptr to be free. */
+	while (intermediate_ptr != NULL) {
+		ret = pthread_cond_wait(&condvar, &mtx);
+		if (ret != 0) {
+			talloc_free(top_ctx);
+			return NULL;
+		}
+	}
+
+	/* and move our memory onto it from our toplevel hierarchy. */
+	intermediate_ptr = talloc_move(NULL, &sub_ctx);
+
+	/* Tell the main thread it's ready for pickup. */
+	pthread_cond_broadcast(&condvar);
+	pthread_mutex_unlock(&mtx);
+
+	talloc_free(top_ctx);
+	return NULL;
+}
+
+/* Main thread. */
+static bool test_pthread_talloc_passing(void)
+{
+	int i;
+	int ret;
+	char str_array[NUM_THREADS][20];
+	pthread_t thread_id;
+	void *mem_ctx;
+
+	/*
+	 * Important ! Null tracking breaks threaded talloc.
+	 * It *must* be turned off.
+	 */
+	talloc_disable_null_tracking();
+
+	printf("test: pthread_talloc_passing\n");
+
+	/* Main thread toplevel context. */
+	mem_ctx = talloc_named_const(NULL, 0, "toplevel");
+	if (mem_ctx == NULL) {
+		return false;
+	}
+
+	/*
+	 * Spin off NUM_THREADS threads.
+	 * They will use their own toplevel contexts.
+	 */
+	for (i = 0; i < NUM_THREADS; i++) {
+		(void)snprintf(str_array[i],
+				20,
+				"thread:%d",
+				i);
+		if (str_array[i] == NULL) {
+			return false;
+		}
+		ret = pthread_create(&thread_id,
+				NULL,
+				thread_fn,
+				str_array[i]);
+		if (ret != 0) {
+			return false;
+		}
+	}
+
+	/* Now wait for NUM_THREADS transfers of the talloc'ed memory. */
+	for (i = 0; i < NUM_THREADS; i++) {
+		ret = pthread_mutex_lock(&mtx);
+		if (ret != 0) {
+			talloc_free(mem_ctx);
+			return false;
+		}
+
+		/* Wait for intermediate_ptr to have our data. */
+		while (intermediate_ptr == NULL) {
+			ret = pthread_cond_wait(&condvar, &mtx);
+			if (ret != 0) {
+				talloc_free(mem_ctx);
+				return false;
+			}
+		}
+
+		/* and move it onto our toplevel hierarchy. */
+		(void)talloc_move(mem_ctx, &intermediate_ptr);
+
+		/* Tell the sub-threads we're ready for another. */
+		pthread_cond_broadcast(&condvar);
+		pthread_mutex_unlock(&mtx);
+	}
+
+	CHECK_SIZE("pthread_talloc_passing", mem_ctx, NUM_THREADS * 100);
+#if 0
+	/* Dump the hierarchy. */
+	talloc_report(mem_ctx, stdout);
+#endif
+	talloc_free(mem_ctx);
+	printf("success: pthread_talloc_passing\n");
+	return true;
+}
+#endif
+
 static void test_reset(void)
 {
 	talloc_set_log_fn(test_log_stdout);
@@ -1771,6 +1912,10 @@ bool torture_local_talloc(struct torture_context *tctx)
 	ret &= test_free_children();
 	test_reset();
 	ret &= test_memlimit();
+#ifdef HAVE_PTHREAD
+	test_reset();
+	ret &= test_pthread_talloc_passing();
+#endif
 
 
 	if (ret) {
diff --git a/lib/talloc/wscript b/lib/talloc/wscript
index 97c52c3..5fadc96 100644
--- a/lib/talloc/wscript
+++ b/lib/talloc/wscript
@@ -80,6 +80,10 @@ def build(bld):
         bld.env.TALLOC_VERSION = VERSION
         private_library = False
 
+        testsuite_deps = 'talloc'
+        if bld.CONFIG_SET('HAVE_PTHREAD'):
+            testsuite_deps += ' pthread'
+
         # should we also install the symlink to libtalloc1.so here?
         bld.SAMBA_LIBRARY('talloc-compat1-%s' % (VERSION),
                           'compat/talloc_compat1.c',
@@ -91,7 +95,7 @@ def build(bld):
 
         bld.SAMBA_BINARY('talloc_testsuite',
                          'testsuite_main.c testsuite.c',
-                         deps='talloc',
+                         testsuite_deps,
                          install=False)
 
     else:
-- 
2.2.0.rc0.207.ga3a616c



More information about the samba-technical mailing list