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
|
/**
@page tevent_request Chapter 4: Tevent request
@section request Tevent request
A specific feature of the library is the tevent request API that provides for
asynchronous computation and allows much more interconnected working and
cooperation among functions and events. When working with tevent request it
is possible to nest one event under another and handle them bit by bit. This
enables the creation of sequences of steps, and provides an opportunity to
prepare for all problems which may unexpectedly happen within the different
phases. One way or another, subrequests split bigger tasks into smaller ones
which allow a clearer view of each task as a whole.
@subsection name Naming conventions
There is a naming convention which is not obligatory but it is followed in this
tutorial:
- Functions triggered before the event happens. These establish a request.
- \b foo_send(...) - this function is called first and it includes the
creation of tevent request - tevent req structure. It does not block
anything, it simply creates a request, sets a callback (foo done) and lets
the program continue
- Functions as a result of event.
- \b foo_done(...) - this function contains code providing for handling itself
and based upon its results, the request is set either as a done or, if an
error occurs, the request is set as a failure.
- \b foo_recv(...) - this function contains code which should, if demanded,
access the result data and make them further visible. The foo state should
be deallocated from memory when the request’s processing is over and
therefore all computed data up to this point would be lost.
As was already mentioned, specific naming subsumes not only functions but also
the data themselves:
- \b foo_state - this is a structure. It contains all the data necessary for
the asynchronous task.
@subsection cr_req Creating a New Asynchronous Request
The first step for working asynchronously is the allocation of memory
requirements. As in previous cases, the talloc context is required, upon which
the asynchronous request will be tied. The next step is the creation of the
request itself.
@code
struct tevent_req* tevent_req_create (TALLOC_CTX *mem_ctx, void **pstate, #type)
@endcode
The pstate is the pointer to the private data. The necessary amount of memory
(based on data type) is allocated during this call. Within this same memory
area all the data from the asynchronous request that need to be preserved for
some time should be kept.
<b>Dealing with a lack of memory</b>
The verification of the returned pointer against NULL is necessary in order to
identify a potential lack of memory. There is a special function which helps
with this check tevent_req_nomem().
It handles verification both of the talloc memory allocation and of the
associated tevent request, and is therefore a very useful function for avoiding
unexpected situations. It can easily be used when checking the availability of
further memory resources that are required for a tevent request. Imagine an
example where additional memory needs arise although no memory resources are
currently available.
@code
bar = talloc(mem_ctx, struct foo);
if(tevent_req_nomem (bar, req)) {
// handling a problem
}
@endcode
This code ensures that the variable bar, which contains NULL as a result of the
unsuccessful satisfaction of its memory requirements, is noticed, and also that
the tevent request req declares it exceeds memory capacity, which implies the
impossibility of finishing the request as originally programmed.
@subsection fini_req Finishing a Request
Marking each request as finished is an essential principle of the tevent
library. Without marking the request as completed - either successfully or with
an error - the tevent loop could not let the appropriate callback be triggered.
It is important to understand that this would be a significant threat, because
it is not usually a question of one single function which prints some text on a
screen, but rather the request is itself probably just a link in a series of
other requests. Stopping one request would stop the others, memory resources
would not be freed, file descriptors might remain open, communication via
socket could be interrupted, and so on. Therefore it is important to think
about finishing requests, either successfully or not, and also to prepare
functions for all possible scenarios, so that the the callbacks do not process
data that are actually invalid or, even worse, in fact non-existent meaning
that a segmentation fault may arise.
<ul>
<li>\b Manually - This is the most common type of finishing request. Calling
this function sets the request as a TEVENT_REQ_DONE. This is the only purpose
of this function and it should be used when everything went well. Typically it
is used within the done functions.
@code
void tevent_req_done (struct tevent_req *req)
@endcode
Alternatively, the request can end up being unsuccessful.
@code
bool tevent_req_error (struct tevent_req *req, uint64_t error)
@endcode
The second argument takes the number of an error (declared by the programmer,
for example in an enumerated variable). The function tevent_req_error() sets
the status of the request as a TEVENT_REQ_USER_ERROR and also stores the code
of error within the structure so it can be used, for example for debugging. The
function returns true, if marking the request as an error was processed with no
problem - value error passed to this function is not equal to 1.</li>
<li>
<b>Setting up a timeout for request</b> - A request can be finished virtually,
or if the process takes too much time, it can be timed out. This is considered
as an error of the request and it leads to calling callback. In the
background, this timeout is set through a time event (described in
@subpage tevent_events ) which eventually triggers an operation marking the
request as a TEVENT_REQ_TIMED_OUT (can not be considered as successfully
finished). In case a time out was already set, this operation will overwrite it
with a new time value (so the timeout may be lengthened) and if everything is
set properly, it returns true.
@code
bool tevent_req_set_endtime(struct tevent_req *req,
struct tevent_context *ev,
struct timeval endtime);
@endcode
</li>
<li><b>Premature Triggering</b> - Imagine a situation in which some part of a
nested subrequest ended up with a failure and it is still required to trigger a
callback. Such as example might result from lack of memory leading to the
impossibility of allocating enough memory requirements for the event to start
processing another subrequest, or from a clear intention to skip other
procedures and trigger the callback regardless of other progress. In these
cases, the function tevent_req_post() is very handy and offers this option.
@code
struct tevent_req* tevent_req_post (struct tevent_req *req,
struct tevent_context *ev);
@endcode
A request finished in this way does not behave as a time event nor as a file
descriptor event but as a immediately scheduled event, and therefore it will be
treated according the description laid down in @subpage tevent_events .
</li>
</ul>
@section nested Subrequests - Nested Requests
To create more complex and interconnected asynchronous operations, it is
possible to submerge a request into another and thus create a so-called
subrequest. Subrequests are not represented by any other special structure but
they are created from tevent_req_create(). This diagram shows the nesting and
life time of each request. The table below describes the same in words, and
shows the triggering of functions during the application run.
<i>Wrapper</i> represents the trigger of the whole cascade of (sub)requests. It
may be e.g. a time or file descriptor event, or another request that was
created at a specific time by the function tevent_wakeup_send() which is a
slightly exceptional method of creating
@code
struct tevent_req *tevent_wakeup_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct timeval wakeup_time);
@endcode
By calling this function, it is possible to create a tevent request which is
actually the return value of this function. In summary, it sets the time value
of the tevent request’s creation. While using this function it is necessary to
use another function in the subrequest’s callback to check for any problems
tevent_wakeup_recv() )
@image html tevent_subrequest.png
A comprehensive example of nested subrequests can be found in the file
echo_server.c. It implements a complete, self-contained echo server with no
dependencies but libevent and libtalloc.
*/
|