tevent  0.9.31
Chapter 4: Tevent 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 func- tions 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.

Naming conventions

There is a naming convention which is not obligatory but it is followed in this tutorial:

As was already mentioned, specific naming subsumes not only functions but also the data themselves:

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.

struct tevent_req* tevent_req_create (TALLOC_CTX *mem_ctx, void **pstate, #type)

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.

Dealing with a lack of memory

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.

bar = talloc(mem_ctx, struct foo);
if(tevent_req_nomem (bar, req)) {
// handling a problem
}

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.

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.

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.

Wrapper 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

struct tevent_req *tevent_wakeup_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct timeval wakeup_time);

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

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.