Otherwise, all of the writes are submitted at once, consuming tons of memory,
and preventing reads from happening in parallel to writes.
Add a semaphore to limit the amount of parallel I/O.
- launch 10,000 concurrent writes
- when any one of these complete, launch a read for the same offset
- compare read/write data
- when all reads complete, terminate
Add integration with the Linux libaio framework:
- an I/O context is initialized
- all asynchronous I/Os request that the kernel notify an eventfd
- a semaphore is used to guard access to the I/O context, so as not to
exceed the maximum parallelism
- an internal function submit_io() is used to submit I/O to the kernel,
returning a future representing completion
- an internal loop running process_io() is used to consume completions
when the eventfd is signalled
This removes the need to create a structure if a future has multiple
return values, and has the nice side effect of removing the specialization
future<void> (replacing it with future<>).
In many cases, we can guess the result of an epoll_wait() before it happens:
- if a read() or write() consumes the entire buffer, a following call
will likely succeed (and if it doesn't, it likely won't)
- after an accept() completes, a write() will likely succeed
Speculatively add these events to events_known; if we mispredict and
fail with EAGAIN, all we need to do is retry.
Reduce calls to epoll_ctl() by allowing epoll events that are not
requested by the user, but have still not been satisfied by the system,
to remain installed in epoll. We may get a spurious wakeup later, but if
we do, we remember it so that when the user does want the event, it will be
ready without a syscall.