The event-driven model doesn't come without any challenges.
Recall that when we talked about the many to one multithreading model,
we said that a single blocking I/O call that's coming from
one of the user level threads can block the entire process,
although there may be other user level threads that are ready to execute.
A similar problem can occur here as well.
If one of the handlers issues a blocking I/O call to read data from
the network or from disk, the entire event-driven process can be blocked.
One way to circumvent this problem, is to use asynchronous I/O operations.
Asynchronous calls have the property that when the system call is made,
the kernel captures enough information about the caller and where and
how the data should be returned once it becomes available.
Async calls also provide the caller with an opportunity to precede
executing something, and then come back at a later time to check if the results
of the asynchronous operation are already available.
For instance, the process or the thread can come back later to check if a file
has already been read and the data is available in the buffer in memory.
One thing that makes asynchronous calls possible is that the OS kernel
is multithreaded.
So while the caller thread continues execution, another kernel thread does
all the necessary work and all the waiting that's needed to perform the I/O
operation, to get the I/O data, and then, to also make sure
that the results become available to the appropriate user level context.
Also, asynchronous operations can benefit by the actual I/O devices.
For instance, the caller thread can simply pass some request data structure
to the device itself, and then the device performs the operation,
and the thread at a later time can come and
check to see whether device has completed the operation.
We will return to a synchronous I/O operations in a later lecture.
What you need to know for
now is that when we're using asynchronous I/O operations,
our process will not be blocked in the kernel when performing I/O.
In the event-driven model,
if the handler initiates an asynchronous I/O operation for network or for
disk, the operating system can simply use the mechanism like select or poll or
epoll like we've mentioned before to catch such events.
Since summary asynchronous I/O operations fit
very nicely with the event-driven model.
The problem with asynchronous I/O calls is that they weren't
ubiquitously available in the past.
And even today, they may not be available for all types of devices.
In a general case, maybe the processing that needs to be performed by our server
isn't to read data from a file, where there are asynchronous system calls.
But instead maybe to call processing some accelerator,
some device that only the server has access to.
To deal with this problem, paper proposed the use of helpers.
But a handler needs to issue an I/O operation that can block,
it passes it to the helper, and returns to the event dispatcher.
The helper will be the one that will handle the blocking I/O operation, and
interact with the dispatcher as necessary.
The communication with the helper can be via socket based interface, or
via another type of messaging interface that's available in operating systems
called pipes.
And both of these present a file descriptor-like interface.
So the same kind of select or
poll mechanism that we mentioned can be used for the event dispatcher
to keep track of various events that are occurring in the system.
This interface can be used to track whether the helpers are providing any
kind of events to the event dispatcher.
In doing this, the synchronous I/O call is handled by the helper.
The helper will be the one that will block, and
the main event dispatcher in the main process will continue uninterrupted.
So this way although we don't have asynchronous I/O calls,
through the use of helpers,
we achieve the same kind of behavior as if we had asynchronous calls.
At the time of the writing of the paper,
another limitation was that not all kernels were multi-threaded.
So basically,
not all kernels supported the one to one model that we talked about.
In order to deal with this limitation,
the decision in the paper was to make these helper entities processes.
Therefore, they call this model AMPED,
Asymmetric Multi-Process Event-Driven model.
It's an event-driven model.
It has multiple processes.
And these processes are asymmetric.
The helper ones only deal with blocking I/O operation.
And then, the main one performs everything else.
In principle, the same kind of idea could have applied to the multi-threaded
scenario where the helpers are threads, not processes,
so asymmetric multi-threaded event-driven model.
And in fact, there is a follow-on on the Flash work that actually does this
exact thing, the AMTED model.
The key benefits of the symmetric model that we described is that it resolved
some of the limitations of the pure event-driven model in
terms of what is required from the operating system,
the dependence on asynchronous I/O calls and threading support.
In addition, this motto let's us achieve concurrency with a smaller memory
footprint than either the multi-process or the multi-threading model.
In the multi-process or multi-threading model,
a worker has to perform everything for a full request.
So its memory requirements will be much more significant than the memory
requirements of a helper entity.
In addition, with the AMPED model, we will have a helper entity only for
the number of concurrent blocking I/O operations.
Whereas, in the multi-threaded or multi-process models,
we will have as many current entities, as many processes, or as many threads
as there are concurrent requests regardless of whether they block or not.
The downside is that audit works well with the server pipe applications.
It is not necessarily as generally applicable to arbitrary applications.
In addition, there are also some complexities with the routing of events
in multi CPU systems.