1tevent_thread(3)                    tevent                    tevent_thread(3)
2
3
4

NAME

6       tevent_thread - Chapter 6: Tevent with threads
7

Tevent with threads

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)
Impressum