Thanks for the patch. This is the same problem as BUG #19622, and Alexandre has a sinval based patch for it in [1].
I read v1, the placement looks good to me.
For the never idle case you named, the check could go after the if/else, next to CHECK_FOR_INTERRUPTS(). Every iteration passes there, and the SMGRRELEASE barrier already calls smgrreleaseall() at that point, so it is a safe place. It only matters for a worker that sees no empty queue for a whole checkpoint cycle, so it is a small thing.
For 18 the idle branch looks different, so the backpatch needs its own version.