According to the libevent book:
Deferred callbacks
By default, a bufferevent callbacks are executed immediately when the corresponding condition happens. (This is true of evbuffer callbacks too; we’ll get to those later.) This immediate invocation can make trouble when dependencies get complex. For example, suppose that there is a callback that moves data into evbuffer A when it grows empty, and another callback that processes data out of evbuffer A when it grows full. Since these calls are all happening on the stack, you might risk a stack overflow if the dependency grows nasty enough.
To solve this, you can tell a bufferevent (or an evbuffer) that its callbacks should be deferred. When the conditions are met for a deferred callback, rather than invoking it immediately, it is queued as part of the event_loop() call, and invoked after the regular events' callbacks.
As described above:
BEV_OPT_DEFER_CALLBACKS
, then it will be processed after all other events in the same batch are processed.Provided two callbacks ca
and cb
. First, ca
is called, ca
finds evbuffer_A
is empty, then writes a message into it.
Then, cb
is called, and cb
finds evbuffer_A
contains a message, then fetch and send it out.
When cb
is called, ca
's stack has been released. I think there won't be a stack overflow in such a scenario.
So, my question is:
What's the purpose of deferred callbacks in libevent?
The example given in the quoted text is a buffer being filled after one event and emptied after another event.
Consider this non-event driven pseudo-code for the same example.
void FillBuffer(...)
{
while (BufferNotFull)
{
PutDataInBuffer(...);
}
If (BufferIsFull) // emulates buffer-full event
{
ReadBuffer(...);
}
... more code ...
}
void ReadBuffer(...)
{
while (BufferNotEmpty)
{
ReadDataFromBuffer(...);
}
If (BufferIsEmpty) // emulates buffer-empty event
{
FillBuffer(...);
}
... more code ...
}
Obviously, the condition of the two if
statements are always true. Consequently the functions never complete but keep calling each other again and again. Sooner or later the stack will overflow.
Now imagine an event driven system where all callbacks happen as soon as the event triggers (i.e. remove the if
-statements but remember that the event-system does the same as the if
-statements). Like:
void FillBuffer(...) // Called on buffer-empty event
{
while (BufferNotFull)
{
PutDataInBuffer(...);
}
// Event buffer-full triggered (i.e. ReadBuffer being called)
... more code ...
}
void ReadBuffer(...) // Called on buffer-full event
{
while (BufferNotEmpty)
{
ReadDataFromBuffer(...);
}
// Event buffer-empty triggered (i.e. FillBuffer being called)
... more code ...
}
Such a system will do the same as the non-event driven pseudo-code, i.e. call the next callback before the first finishes.
Deferred callbacks can solve the problem by letting one callback complete before calling the next callback.