summaryrefslogtreecommitdiffstats
path: root/lib/tevent/doc/tevent_thread.dox
blob: 875dae875d12137a9f55180261a48edbbeb42e78 (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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/**
@page tevent_thread Chapter 6: Tevent with threads

@section threads Tevent with threads

In order to use tevent with threads, you must first understand
how to use the talloc library in threaded programs. For more
information about working with talloc, please visit <a
href="https://talloc.samba.org/">talloc website</a> where tutorial and
documentation are located.

If a tevent context structure is talloced from a NULL, thread-safe talloc
context, then it can be safe to use in a threaded program. The function
<code>talloc_disable_null_tracking()</code> <b>must</b> be called from the initial
program thread before any talloc calls are made to ensure talloc is thread-safe.

Each thread must create it's own tevent context structure as follows
<code>tevent_context_init(NULL)</code> and no talloc memory contexts
can be shared between threads.

Separate threads using tevent in this way can communicate
by writing data into file descriptors that are being monitored
by a tevent context on another thread. For example (simplified
with no error handling):

@code
Main thread:

main()
{
	talloc_disable_null_tracking();

	struct tevent_context *master_ev = tevent_context_init(NULL);
	void *mem_ctx = talloc_new(master_ev);

	// Create file descriptor to monitor.
	int pipefds[2];

	pipe(pipefds);

	struct tevent_fd *fde = tevent_add_fd(master_ev,
				mem_ctx,
				pipefds[0], // read side of pipe
				TEVENT_FD_READ,
				pipe_read_handler, // callback function
				private_data_pointer);

	// Create sub thread, pass pipefds[1] write side of pipe to it.
	// The above code not shown here..

	// Process events.
	tevent_loop_wait(master_ev);

	// Cleanup if loop exits.
	talloc_free(master_ev);
}

@endcode

When the subthread writes to pipefds[1], the function
<code>pipe_read_handler()</code> will be called in the main thread.

@subsection More sophisticated use

A popular way to use an event library within threaded programs
is to allow a sub-thread to asynchronously schedule a tevent_immediate
function call from the event loop of another thread. This can be built
out of the basic functions and isolation mechanisms of tevent,
but tevent also comes with some utility functions that make
this easier, so long as you understand the limitations that
using threads with talloc and tevent impose.

To allow a tevent context to receive an asynchronous tevent_immediate
function callback from another thread, create a struct tevent_thread_proxy *
by calling @code

struct tevent_thread_proxy *tevent_thread_proxy_create(
                struct tevent_context *dest_ev_ctx);

@endcode

This function allocates the internal data structures to
allow asynchronous callbacks as a talloc child of the
struct tevent_context *, and returns a struct tevent_thread_proxy *
that can be passed to another thread.

When you have finished receiving asynchronous callbacks, simply
talloc_free the struct tevent_thread_proxy *, or talloc_free
the struct tevent_context *, which will deallocate the resources
used.

To schedule an asynchronous tevent_immediate function call from one
thread on the tevent loop of another thread, use
@code

void tevent_thread_proxy_schedule(struct tevent_thread_proxy *tp,
                                struct tevent_immediate **pp_im,
                                tevent_immediate_handler_t handler,
                                void **pp_private_data);

@endcode

This function causes the function <code>handler()</code>
to be invoked as a tevent_immediate callback from the event loop
of the thread that created the struct tevent_thread_proxy *
(so the owning <code>struct tevent_context *</code> should be
long-lived and not in the process of being torn down).

The <code>struct tevent_thread_proxy</code> object being
used here is a child of the event context of the target
thread. So external synchronization mechanisms must be
used to ensure that the target object is still in use
at the time of the <code>tevent_thread_proxy_schedule()</code>
call. In the example below, the request/response nature
of the communication ensures this.

The <code>struct tevent_immediate **pp_im</code> passed into this function
should be a struct tevent_immediate * allocated on a talloc context
local to this thread, and will be reparented via talloc_move
to be owned by <code>struct tevent_thread_proxy *tp</code>.
<code>*pp_im</code> will be set to NULL on successful scheduling
of the tevent_immediate call.

<code>handler()</code> will be called as a normal tevent_immediate
callback from the <code>struct tevent_context *</code> of the destination
event loop that created the <code>struct tevent_thread_proxy *</code>

Returning from this functions does not mean that the <code>handler</code>
has been invoked, merely that it has been scheduled to be called in the
destination event loop.

Because the calling thread does not wait for the
callback to be scheduled and run on the destination
thread, this is a fire-and-forget call. If you wish
confirmation of the <code>handler()</code> being
successfully invoked, you must ensure it replies to the
caller in some way.

Because of asynchronous nature of this call, the nature
of the parameter passed to the destination thread has some
restructions. If you don't need parameters, merely pass
<code>NULL</code> as the value of
<code>void **pp_private_data</code>.

If you wish to pass a pointer to data between the threads,
it <b>MUST</b> be a pointer to a talloced pointer, which is
not part of a talloc-pool, and it must not have a destructor
attached. The ownership of the memory pointed to will
be passed from the calling thread to the tevent library,
and if the receiving thread does not talloc-reparent
it to its own contexts, it will be freed once the
<code>handler</code> is called.

On success, <code>*pp_private</code> will be <code>NULL</code>
to signify the talloc memory ownership has been moved.

In practice for message passing between threads in
event loops these restrictions are not very onerous.

The easiest way to to a request-reply pair between
tevent loops on different threads is to pass the
parameter block of memory back and forth using
a reply <code>tevent_thread_proxy_schedule()</code>
call.

Here is an example (without error checking for
simplicity):

@code
------------------------------------------------
// Master thread.

main()
{
	// Make talloc thread-safe.

	talloc_disable_null_tracking();

	// Create the master event context.

	struct tevent_context *master_ev = tevent_context_init(NULL);

	// Create the master thread proxy to allow it to receive
	// async callbacks from other threads.

	struct tevent_thread_proxy *master_tp =
			tevent_thread_proxy_create(master_ev);

	// Create sub-threads, passing master_tp in
	// some way to them.
	// This code not shown..

	// Process events.
	// Function master_callback() below
	// will be invoked on this thread on
	// master_ev event context.

	tevent_loop_wait(master_ev);

	// Cleanup if loop exits.

	talloc_free(master_ev);
}

// Data passed between threads.
struct reply_state {
	struct tevent_thread_proxy *reply_tp;
	pthread_t thread_id;
	bool *p_finished;
};

// Callback Called in child thread context.

static void thread_callback(struct tevent_context *ev,
                                struct tevent_immediate *im,
                                void *private_ptr)
{
	// Move the ownership of what private_ptr
	// points to from the tevent library back to this thread.

	struct reply_state *rsp =
		talloc_get_type_abort(private_ptr, struct reply_state);

	talloc_steal(ev, rsp);

	*rsp->p_finished = true;

	// im will be talloc_freed on return from this call.
	// but rsp will not.
}

// Callback Called in master thread context.

static void master_callback(struct tevent_context *ev,
                                struct tevent_immediate *im,
                                void *private_ptr)
{
	// Move the ownership of what private_ptr
	// points to from the tevent library to this thread.

	struct reply_state *rsp =
		talloc_get_type_abort(private_ptr, struct reply_state);

	talloc_steal(ev, rsp);

	printf("Callback from thread %s\n", thread_id_to_string(rsp->thread_id));

	/* Now reply to the thread ! */
	tevent_thread_proxy_schedule(rsp->reply_tp,
				&im,
				thread_callback,
				&rsp);

	// Note - rsp and im are now NULL as the tevent library
	// owns the memory.
}

// Child thread.

static void *thread_fn(void *private_ptr)
{
	struct tevent_thread_proxy *master_tp =
		talloc_get_type_abort(private_ptr, struct tevent_thread_proxy);
	bool finished = false;
	int ret;

	// Create our own event context.

	struct tevent_context *ev = tevent_context_init(NULL);

	// Create the local thread proxy to allow us to receive
	// async callbacks from other threads.

	struct tevent_thread_proxy *local_tp =
			tevent_thread_proxy_create(master_ev);

	// Setup the data to send.

	struct reply_state *rsp = talloc(ev, struct reply_state);

	rsp->reply_tp = local_tp;
	rsp->thread_id = pthread_self();
	rsp->p_finished = &finished;

	// Create the immediate event to use.

	struct tevent_immediate *im = tevent_create_immediate(ev);

	// Call the master thread.

	tevent_thread_proxy_schedule(master_tp,
				&im,
				master_callback,
				&rsp);

	// Note - rsp and im are now NULL as the tevent library
	// owns the memory.

	// Wait for the reply.

	while (!finished) {
		tevent_loop_once(ev);
	}

	// Cleanup.

	talloc_free(ev);
	return NULL;
}

@endcode

Note this doesn't have to be a master-subthread communication.
Any thread that has access to the <code>struct tevent_thread_proxy *</code>
pointer of another thread that has called <code>tevent_thread_proxy_create()
</code> can send an async tevent_immediate request.

But remember the caveat that external synchronization must be used
to ensure the target <code>struct tevent_thread_proxy *</code> object
exists at the time of the <code>tevent_thread_proxy_schedule()</code>
call or unreproducible crashes will result.
*/