From: Shai Fultheim Date: Sun, 17 May 2026 08:27:00 +0000 (+0300) Subject: crimson/os/seastore: yield to user IO between cleaner cycles X-Git-Tag: v21.0.1~157^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F68961%2Fhead;p=ceph.git crimson/os/seastore: yield to user IO between cleaner cycles After the deadlock fix in the preceding commit ("fix IO-block deadlock when cleaner is sleeping"), the cleaner stays awake while user IO is blocked, but a second symptom appears at high alive_ratio (~0.79): the cleaner's segment-allocate-and-fill loop runs tightly enough that the user-IO continuation scheduled by maybe_wake_blocked_io() never gets a chance to retry try_reserve_io() before the cleaner consumes the projected_avail headroom again on its next iteration. User IO wakes, sees projected_avail still below hard_limit, re-blocks immediately. In the qa/standalone/crimson randwrite bench this manifests as: cluster makes 500-700 GB of progress, then user_written counter freezes for ~75 seconds (watchdog window) while the cleaner is fully busy. In BackgroundProcess::run(), after each do_background_cycle, if user IO is currently blocked, yield to the reactor. That gives the woken user-IO continuation a chance to slot in and complete a reservation before the cleaner starts its next reservation-consuming cycle. With this change, the same bench runs 19 minutes (vs 11-16 min) and writes 785 GB user (vs 506-692 GB) before the next cluster limit hits, which is the inherent throughput cap at alive_ratio 0.79 where each reclaim only frees ~21% of segment size — not a coordination bug. Signed-off-by: Shai Fultheim --- diff --git a/src/crimson/os/seastore/extent_placement_manager.cc b/src/crimson/os/seastore/extent_placement_manager.cc index c579b5fd14f..928b8d37966 100644 --- a/src/crimson/os/seastore/extent_placement_manager.cc +++ b/src/crimson/os/seastore/extent_placement_manager.cc @@ -807,6 +807,11 @@ ExtentPlacementManager::BackgroundProcess::maybe_wake_blocked_io() DEBUG(""); blocking_io->set_value(); blocking_io = std::nullopt; + // Remember that we just woke a blocked IO; run() yields once on + // this edge so the woken continuation has a chance to retry the + // reservation before the cleaner spins another cycle and consumes + // the projected_avail headroom we just freed. + pending_user_io_wake = true; } } @@ -818,6 +823,12 @@ ExtentPlacementManager::BackgroundProcess::run() if (background_should_run()) { log_state("run(background)"); co_await do_background_cycle(); + // Edge-triggered: yield only when a blocked IO was actually woken, so the + // resumed continuation retries try_reserve_io() before the next cycle runs. + if (pending_user_io_wake) { + pending_user_io_wake = false; + co_await seastar::yield(); + } } else { log_state("run(block)"); assert(!blocking_background); diff --git a/src/crimson/os/seastore/extent_placement_manager.h b/src/crimson/os/seastore/extent_placement_manager.h index 123826f16be..df32bcd7ba1 100644 --- a/src/crimson/os/seastore/extent_placement_manager.h +++ b/src/crimson/os/seastore/extent_placement_manager.h @@ -1124,6 +1124,11 @@ private: std::optional> process_join; std::optional> blocking_background; std::optional> blocking_io; + // Set by maybe_wake_blocked_io() whenever it actually unblocks a + // user IO; consumed by run() to yield exactly once on that edge, + // giving the woken continuation a chance to retry the reservation + // before the next background cycle. + bool pending_user_io_wake = false; bool is_running_until_halt = false; state_t state = state_t::STOP; eviction_state_t eviction_state;