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