A request is completed with the queue lock dropped, because the
completion callback may submit the next request and take that lock
again. The completion scan checked a slot under the lock, dropped the
lock, and only then completed the request, with the slot still marked
scheduled. The failure path and the abort completion look for such
slots under the same lock, so one of them could pick the same slot in
that window. The request was then completed twice, freed twice, and
its CCB went back to CAM twice. After a reset every in-flight slot
reads as done at once, so the window was easy to hit.
No existing state could mark that window. FREE would hand the slot to
a new request while the old completion still reads it, RESERVED means
a submit in progress, and NEED_ERROR_HANDLING is about to mean a
request that a reset has claimed but nobody has completed yet.
Add a completing state. Every path that completes a slot sets it
under the lock before dropping the lock, and every path skips a slot
in that state. The abort completion now takes only a slot that is
still scheduled, where it used to take anything that was not free.
Sponsored by: Samsung Electronics