This solution seems problematic to me. Won't we end up with queries that continue to run needlessly after the requested records have been returned? On large tables, this could result in a lot of disk and network IO.
I think what we really need to do is solve the problem of terminating a query. Maybe it should happen like this:
Limit reaches the end, return IterOutcome.NONE. It does not call incoming.next() to drain incoming buffers.
After the result has been sent to the client, the foreman informs the leaf fragments that they should stop execution if they haven't already. The foreman will inform all intermediate fragments, and finally close the root fragment.
Part of closing each of these fragment will be ensuring that any batch in the IncomingBuffers queue get released. And we shouldn't have to worry about any batches that arrive after the fragment has closed, because there will no longer be a socket connection to receive them.