-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
reader_concurrency_semaphore: handle read blocked on memory being registered as inactive #12777
reader_concurrency_semaphore: handle read blocked on memory being registered as inactive #12777
Conversation
First two commits are from #12756, will rebase them away, once said PR is merged. |
dbf8fce
to
e968a01
Compare
CI state |
e968a01
to
3f546cc
Compare
CI state |
Before reviewing, the gist is to convert a chain of futures to a chain of objects? If so that's pretty much what we've been doing for years while taming those queues. |
I did not change how waiting work. What I did was throw out |
3f546cc
to
57c30f5
Compare
v1.1:
|
57c30f5
to
0b1b1e0
Compare
v1.2:
|
CI state |
Looks good. I think the move to intrusive_list was worthwhile even without fixing the bug, reader_concurrency_semaphore is central enough that we can use customized data types for it. |
0b1b1e0
to
0e7cc87
Compare
Rebased (but #12756 is still not merged). |
CI state |
…oexcept It is in fact noexcept and so it is expected to be, so document this.
It already is conceptually, as it passes const references to the permits it iterates over. The only reason it wasn't const before is a technical issue which is solved here with a const_cast.
… permit list param This param is from a time when _permit_list was not accessible from the outside, so it was passed along the semaphore instance to avoid making the diagnostics methods friends. To allow the semaphore freedom in how permits are stored, the diagnostics code is instead made to use foreach_permit(), instead of accessing the underlying list directly. As the diagnostics code wants reader_permit::impl& directly, a new variant of foreach_permit() passing impl references is introduced.
0e7cc87
to
f226965
Compare
Instead of having callers use get_timeout(), then compare it against the current time, set up a timeout timer in the permit, which assigned a new `_ex` member (a `std::exception_ptr`) to the appropriate exception type when it fires. Callers can now just poll check_abort() which will throw when `_ex` is not null. This is more natural and allows for more general reasons for aborting reads in the future. This prepares the ground for timeouts being managed inside the permit, instead of by the semaphore. Including timing out while in a wait queue.
Use it to keep track of all permits that are currently waiting on something: admission, memory or execution. Currently we keep track of size, by adding up the result of size() of the various queues. In future patches we are going to change the queues such that they will not have constant time size anymore, move to an explicit counter in preperation to that. Another change this commit makes is to also include ready list entries in this counter. Permits in the ready list are also waiters, they wait to be executed. Soon we will have a separate wait state for this too.
There is now a field in stats with the same information, use that.
Currently the reader_permit has some private methods that only the semaphore's internal calls. But this method of communication is not consistent, other times the semaphore accesses the permit impl directly, calling methods on that. This commit introduces operator * and -> for reader_permit. With this, the semaphore internals always call the reader_permit::impl methods direcly, either via a direct reference, or via the above operators. This makes the permit internface a little narrower and reduces boilerplate code.
Instead of the `entry` wrapper. In _wait_list and _ready_list, that is. Data stored in the `entry` wrapper is moved to a new `reader_permit::auxiliary_data` type. This makes the reader permit self-sufficient. This in turn prepares the ground for the ability to de-queue a permit from any queue, with nothing but a permit reference at hand: no need to have back pointer to wrappers and/or iterators.
They will soon depend on the definition of the reader_permit::impl, which is only available in the .cc file.
Instead of using expiring_fifo to store queued permits, use the same intrusive list mechanism we use to keep track of all permits. Permits are now moved between the _permit_list and the wait queues, depending on which state they are in. This means _permit_list is now not the definitive list containing all permits, instead it is the list containing all permits that are not in a more specialized queue at the moment. Code wishing to iterate over all permits should now use foreach_permits(). For outside code, this was already the only way and internal users are already patched. Making the wait lists intrusive allows us to dequeue a permit from any position, with nothing but a permit reference at hand. It also means the wait queues don't have any additional memory requirements, other than the memory for the permit itself. Timeout while being queued is now handled by the permit's on_timeout() callback.
Used while the permit is in the _ready_list, waiting for the execution loop to pick it up. This just acknowledging the existence of this wait-state. This state will now show up in permit diagnostics printouts and we can now determine whether a permit is waiting for execution, without checking which queue it is in.
f226965
to
d2bc540
Compare
New in v2:
|
Before
After:
Hmm, looks like we improved on on instructions and therefore on tps but degraded on tasks/op (and therefore allocas/op). |
I think I know where the extra continuations is coming from, I will look into getting rid of it. |
CI state |
d2bc540
to
abad372
Compare
v3:
Before:
After:
|
CI state |
Now we improved on instructions but degraded on passing the tests. |
Following the same scheme we used to make the wait lists intrusive. Permits are added to the ready list intrusive list while waiting to be executed and moved back to the _permit_list when de-queued from this list. We now use a conditional variable for signaling when there are permits ready to be executed.
They will soon need to access reader_permit::impl internals, only available in the .cc file.
Add an member of type `inactive_read` to reader permit, and store permit instances in `_inactive_reads`. This list is now just another intrusive list the permit can be linked into, depending on its state. Inactive read handles now just store a reader permit pointer.
It is not used in the header anymore and moving it to the .cc allows us to remove the dependency on flat_mutation_reader_v2.hh.
If the read is inactive when the timeout clock fires, evict it. Now that `_inactive_reads` just store permits, we can do this easily.
A mostly cosmetic change. Also add a comment mentioning that this is the catch-all list.
…g inactive Kill said read's memory requests with std::bad_alloc and dequeue it from the memory wait list, then evict it on the spot. Now that `_inactive_reads` just store permits, we can do this easily.
v4:
|
abad372
to
3f0b348
Compare
CI state |
Ping. |
A read that requested memory and has to wait for it can be registered as inactive. This can happen for example if the memory request originated from a background I/O operation (a read-ahead maybe).
Handling this case is currently very difficult. What we want to do is evict such a read on-the-spot: the fact that there is a read waiting on memory means memory is in demand and so inactive reads should be evicted. To evict this reader, we'd first have to remove it from the memory wait list, which is almost impossible currently, because
expiring_fifo<>
, the type used for the wait list, doesn't allow for that. So in this PR we set out to make this possible first, by transforming all current queues to be intrusive lists of permits. Permits are already linked into an intrusive list, to allow for enumerating all existing permits. We use these existing hooks to link the permits into the appropriate queue, and back to_permit_list
when they are not in any special queue. To make this possible we first have to make all lists store naked permits, moving all auxiliary data fields currently stored in wrappers likeentry
into the permit itself. With this, all queues and lists in the semaphore are intrusive lists, storing permits directly, which has the following implications:reader_concurrenc_semaphore::with_permit()
) had to be adjusted.After all that extensive preparations, we can now handle the case of evicting a reader which is queued on memory.
Fixes: #12700