1tevent_thread(3) tevent tevent_thread(3)
2
3
4
6 tevent_thread - Chapter 6: Tevent with threads
7
8
10 In order to use tevent with threads, you must first understand how to
11 use the talloc library in threaded programs. For more information about
12 working with talloc, please visit talloc website where tutorial and
13 documentation are located.
14
15 If a tevent context structure is talloced from a NULL, thread-safe
16 talloc context, then it can be safe to use in a threaded program. The
17 function talloc_disable_null_tracking() must be called from the initial
18 program thread before any talloc calls are made to ensure talloc is
19 thread-safe.
20
21 Each thread must create it's own tevent context structure as follows
22 tevent_context_init(NULL) and no talloc memory contexts can be shared
23 between threads.
24
25 Separate threads using tevent in this way can communicate by writing
26 data into file descriptors that are being monitored by a tevent context
27 on another thread. For example (simplified with no error handling):
28
29 Main thread:
30
31 main()
32 {
33 talloc_disable_null_tracking();
34
35 struct tevent_context *master_ev = tevent_context_init(NULL);
36 void *mem_ctx = talloc_new(master_ev);
37
38 // Create file descriptor to monitor.
39 int pipefds[2];
40
41 pipe(pipefds);
42
43 struct tevent_fd *fde = tevent_add_fd(master_ev,
44 mem_ctx,
45 pipefds[0], // read side of pipe
46 TEVENT_FD_READ,
47 pipe_read_handler, // callback function
48 private_data_pointer);
49
50 // Create sub thread, pass pipefds[1] write side of pipe to it.
51 // The above code not shown here..
52
53 // Process events.
54 tevent_loop_wait(master_ev);
55
56 // Cleanup if loop exits.
57 talloc_free(master_ev);
58 }
59
60 When the subthread writes to pipefds[1], the function
61 pipe_read_handler() will be called in the main thread.
62
63 sophisticated use
64 A popular way to use an event library within threaded programs is to
65 allow a sub-thread to asynchronously schedule a tevent_immediate
66 function call from the event loop of another thread. This can be built
67 out of the basic functions and isolation mechanisms of tevent, but
68 tevent also comes with some utility functions that make this easier, so
69 long as you understand the limitations that using threads with talloc
70 and tevent impose.
71
72 To allow a tevent context to receive an asynchronous tevent_immediate
73 function callback from another thread, create a struct
74 tevent_thread_proxy * by calling
75
76 struct tevent_thread_proxy *tevent_thread_proxy_create(
77 struct tevent_context *dest_ev_ctx);
78
79
80 This function allocates the internal data structures to allow
81 asynchronous callbacks as a talloc child of the struct tevent_context
82 *, and returns a struct tevent_thread_proxy * that can be passed to
83 another thread.
84
85 When you have finished receiving asynchronous callbacks, simply
86 talloc_free the struct tevent_thread_proxy *, or talloc_free the struct
87 tevent_context *, which will deallocate the resources used.
88
89 To schedule an asynchronous tevent_immediate function call from one
90 thread on the tevent loop of another thread, use
91
92 void tevent_thread_proxy_schedule(struct tevent_thread_proxy *tp,
93 struct tevent_immediate **pp_im,
94 tevent_immediate_handler_t handler,
95 void **pp_private_data);
96
97
98 This function causes the function handler() to be invoked as a
99 tevent_immediate callback from the event loop of the thread that
100 created the struct tevent_thread_proxy * (so the owning struct
101 tevent_context * should be long-lived and not in the process of being
102 torn down).
103
104 The struct tevent_thread_proxy object being used here is a child of the
105 event context of the target thread. So external synchronization
106 mechanisms must be used to ensure that the target object is still in
107 use at the time of the tevent_thread_proxy_schedule() call. In the
108 example below, the request/response nature of the communication ensures
109 this.
110
111 The struct tevent_immediate **pp_im passed into this function should be
112 a struct tevent_immediate * allocated on a talloc context local to this
113 thread, and will be reparented via talloc_move to be owned by struct
114 tevent_thread_proxy *tp. *pp_im will be set to NULL on successful
115 scheduling of the tevent_immediate call.
116
117 handler() will be called as a normal tevent_immediate callback from the
118 struct tevent_context * of the destination event loop that created the
119 struct tevent_thread_proxy *
120
121 Returning from this functions does not mean that the handler has been
122 invoked, merely that it has been scheduled to be called in the
123 destination event loop.
124
125 Because the calling thread does not wait for the callback to be
126 scheduled and run on the destination thread, this is a fire-and-forget
127 call. If you wish confirmation of the handler() being successfully
128 invoked, you must ensure it replies to the caller in some way.
129
130 Because of asynchronous nature of this call, the nature of the
131 parameter passed to the destination thread has some restructions. If
132 you don't need parameters, merely pass NULL as the value of void
133 **pp_private_data.
134
135 If you wish to pass a pointer to data between the threads, it MUST be a
136 pointer to a talloced pointer, which is not part of a talloc-pool, and
137 it must not have a destructor attached. The ownership of the memory
138 pointed to will be passed from the calling thread to the tevent
139 library, and if the receiving thread does not talloc-reparent it to its
140 own contexts, it will be freed once the handler is called.
141
142 On success, *pp_private will be NULL to signify the talloc memory
143 ownership has been moved.
144
145 In practice for message passing between threads in event loops these
146 restrictions are not very onerous.
147
148 The easiest way to to a request-reply pair between tevent loops on
149 different threads is to pass the parameter block of memory back and
150 forth using a reply tevent_thread_proxy_schedule() call.
151
152 Here is an example (without error checking for simplicity):
153
154 ------------------------------------------------
155 // Master thread.
156
157 main()
158 {
159 // Make talloc thread-safe.
160
161 talloc_disable_null_tracking();
162
163 // Create the master event context.
164
165 struct tevent_context *master_ev = tevent_context_init(NULL);
166
167 // Create the master thread proxy to allow it to receive
168 // async callbacks from other threads.
169
170 struct tevent_thread_proxy *master_tp =
171 tevent_thread_proxy_create(master_ev);
172
173 // Create sub-threads, passing master_tp in
174 // some way to them.
175 // This code not shown..
176
177 // Process events.
178 // Function master_callback() below
179 // will be invoked on this thread on
180 // master_ev event context.
181
182 tevent_loop_wait(master_ev);
183
184 // Cleanup if loop exits.
185
186 talloc_free(master_ev);
187 }
188
189 // Data passed between threads.
190 struct reply_state {
191 struct tevent_thread_proxy *reply_tp;
192 pthread_t thread_id;
193 bool *p_finished;
194 };
195
196 // Callback Called in child thread context.
197
198 static void thread_callback(struct tevent_context *ev,
199 struct tevent_immediate *im,
200 void *private_ptr)
201 {
202 // Move the ownership of what private_ptr
203 // points to from the tevent library back to this thread.
204
205 struct reply_state *rsp =
206 talloc_get_type_abort(private_ptr, struct reply_state);
207
208 talloc_steal(ev, rsp);
209
210 *rsp->p_finished = true;
211
212 // im will be talloc_freed on return from this call.
213 // but rsp will not.
214 }
215
216 // Callback Called in master thread context.
217
218 static void master_callback(struct tevent_context *ev,
219 struct tevent_immediate *im,
220 void *private_ptr)
221 {
222 // Move the ownership of what private_ptr
223 // points to from the tevent library to this thread.
224
225 struct reply_state *rsp =
226 talloc_get_type_abort(private_ptr, struct reply_state);
227
228 talloc_steal(ev, rsp);
229
230 printf("Callback from thread %s0, thread_id_to_string(rsp->thread_id));
231
232 /* Now reply to the thread ! */
233 tevent_thread_proxy_schedule(rsp->reply_tp,
234 &im,
235 thread_callback,
236 &rsp);
237
238 // Note - rsp and im are now NULL as the tevent library
239 // owns the memory.
240 }
241
242 // Child thread.
243
244 static void *thread_fn(void *private_ptr)
245 {
246 struct tevent_thread_proxy *master_tp =
247 talloc_get_type_abort(private_ptr, struct tevent_thread_proxy);
248 bool finished = false;
249 int ret;
250
251 // Create our own event context.
252
253 struct tevent_context *ev = tevent_context_init(NULL);
254
255 // Create the local thread proxy to allow us to receive
256 // async callbacks from other threads.
257
258 struct tevent_thread_proxy *local_tp =
259 tevent_thread_proxy_create(master_ev);
260
261 // Setup the data to send.
262
263 struct reply_state *rsp = talloc(ev, struct reply_state);
264
265 rsp->reply_tp = local_tp;
266 rsp->thread_id = pthread_self();
267 rsp->p_finished = &finished;
268
269 // Create the immediate event to use.
270
271 struct tevent_immediate *im = tevent_create_immediate(ev);
272
273 // Call the master thread.
274
275 tevent_thread_proxy_schedule(master_tp,
276 &im,
277 master_callback,
278 &rsp);
279
280 // Note - rsp and im are now NULL as the tevent library
281 // owns the memory.
282
283 // Wait for the reply.
284
285 while (!finished) {
286 tevent_loop_once(ev);
287 }
288
289 // Cleanup.
290
291 talloc_free(ev);
292 return NULL;
293 }
294
295 Note this doesn't have to be a master-subthread communication. Any
296 thread that has access to the struct tevent_thread_proxy * pointer of
297 another thread that has called tevent_thread_proxy_create() can send
298 an async tevent_immediate request.
299
300 But remember the caveat that external synchronization must be used to
301 ensure the target struct tevent_thread_proxy * object exists at the
302 time of the tevent_thread_proxy_schedule() call or unreproducible
303 crashes will result.
304
305
306
307Version 0.9.8 Tue Jan 26 2021 tevent_thread(3)