The current readahead accomplishes its task by using an async reentrant readahead. We get away with this today because the background generator is async reentrant because it runs on a thread pool of size 1 and so the underlying thread pool can only iterate the underlying iterator synchronously.
However, in order to move to the I/O thread pool we need a readahead operator that is does not pull reentrantly.
This can be done either by submitting the next task as soon as the previous one is done or by creating a "serial concurrency" executor that wraps an underlying executor. Both approaches will need a queue so this can be done after