summaryrefslogtreecommitdiffstats
path: root/lib/talloc/doc/tutorial_threads.dox
blob: 111bbf587aaf76f345953facee8419e0ca230ecb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/**
@page libtalloc_threads Chapter 8: Using threads with talloc

@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:

@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;
}
@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.

@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;
}
@endcode
*/