Skip to content
This repository was archived by the owner on Apr 8, 2024. It is now read-only.

Commit 34ca080

Browse files
htejungregkh
authored andcommitted
iocost: protect iocg->abs_vdebt with iocg->waitq.lock
commit 0b80f9866e6bbfb905140ed8787ff2af03652c0c upstream. abs_vdebt is an atomic_64 which tracks how much over budget a given cgroup is and controls the activation of use_delay mechanism. Once a cgroup goes over budget from forced IOs, it has to pay it back with its future budget. The progress guarantee on debt paying comes from the iocg being active - active iocgs are processed by the periodic timer, which ensures that as time passes the debts dissipate and the iocg returns to normal operation. However, both iocg activation and vdebt handling are asynchronous and a sequence like the following may happen. 1. The iocg is in the process of being deactivated by the periodic timer. 2. A bio enters ioc_rqos_throttle(), calls iocg_activate() which returns without anything because it still sees that the iocg is already active. 3. The iocg is deactivated. 4. The bio from #2 is over budget but needs to be forced. It increases abs_vdebt and goes over the threshold and enables use_delay. 5. IO control is enabled for the iocg's subtree and now IOs are attributed to the descendant cgroups and the iocg itself no longer issues IOs. This leaves the iocg with stuck abs_vdebt - it has debt but inactive and no further IOs which can activate it. This can end up unduly punishing all the descendants cgroups. The usual throttling path has the same issue - the iocg must be active while throttled to ensure that future event will wake it up - and solves the problem by synchronizing the throttling path with a spinlock. abs_vdebt handling is another form of overage handling and shares a lot of characteristics including the fact that it isn't in the hottest path. This patch fixes the above and other possible races by strictly synchronizing abs_vdebt and use_delay handling with iocg->waitq.lock. Signed-off-by: Tejun Heo <tj@kernel.org> Reported-by: Vlad Dmitriev <vvd@fb.com> Cc: stable@vger.kernel.org # v5.4+ Fixes: e1518f6 ("blk-iocost: Don't let merges push vtime into the future") Signed-off-by: Jens Axboe <axboe@kernel.dk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent d8c7f01 commit 34ca080

2 files changed

Lines changed: 77 additions & 47 deletions

File tree

block/blk-iocost.c

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ struct ioc_gq {
469469
*/
470470
atomic64_t vtime;
471471
atomic64_t done_vtime;
472-
atomic64_t abs_vdebt;
472+
u64 abs_vdebt;
473473
u64 last_vtime;
474474

475475
/*
@@ -1145,7 +1145,7 @@ static void iocg_kick_waitq(struct ioc_gq *iocg, struct ioc_now *now)
11451145
struct iocg_wake_ctx ctx = { .iocg = iocg };
11461146
u64 margin_ns = (u64)(ioc->period_us *
11471147
WAITQ_TIMER_MARGIN_PCT / 100) * NSEC_PER_USEC;
1148-
u64 abs_vdebt, vdebt, vshortage, expires, oexpires;
1148+
u64 vdebt, vshortage, expires, oexpires;
11491149
s64 vbudget;
11501150
u32 hw_inuse;
11511151

@@ -1155,18 +1155,15 @@ static void iocg_kick_waitq(struct ioc_gq *iocg, struct ioc_now *now)
11551155
vbudget = now->vnow - atomic64_read(&iocg->vtime);
11561156

11571157
/* pay off debt */
1158-
abs_vdebt = atomic64_read(&iocg->abs_vdebt);
1159-
vdebt = abs_cost_to_cost(abs_vdebt, hw_inuse);
1158+
vdebt = abs_cost_to_cost(iocg->abs_vdebt, hw_inuse);
11601159
if (vdebt && vbudget > 0) {
11611160
u64 delta = min_t(u64, vbudget, vdebt);
11621161
u64 abs_delta = min(cost_to_abs_cost(delta, hw_inuse),
1163-
abs_vdebt);
1162+
iocg->abs_vdebt);
11641163

11651164
atomic64_add(delta, &iocg->vtime);
11661165
atomic64_add(delta, &iocg->done_vtime);
1167-
atomic64_sub(abs_delta, &iocg->abs_vdebt);
1168-
if (WARN_ON_ONCE(atomic64_read(&iocg->abs_vdebt) < 0))
1169-
atomic64_set(&iocg->abs_vdebt, 0);
1166+
iocg->abs_vdebt -= abs_delta;
11701167
}
11711168

11721169
/*
@@ -1222,12 +1219,18 @@ static bool iocg_kick_delay(struct ioc_gq *iocg, struct ioc_now *now, u64 cost)
12221219
u64 expires, oexpires;
12231220
u32 hw_inuse;
12241221

1222+
lockdep_assert_held(&iocg->waitq.lock);
1223+
12251224
/* debt-adjust vtime */
12261225
current_hweight(iocg, NULL, &hw_inuse);
1227-
vtime += abs_cost_to_cost(atomic64_read(&iocg->abs_vdebt), hw_inuse);
1226+
vtime += abs_cost_to_cost(iocg->abs_vdebt, hw_inuse);
12281227

1229-
/* clear or maintain depending on the overage */
1230-
if (time_before_eq64(vtime, now->vnow)) {
1228+
/*
1229+
* Clear or maintain depending on the overage. Non-zero vdebt is what
1230+
* guarantees that @iocg is online and future iocg_kick_delay() will
1231+
* clear use_delay. Don't leave it on when there's no vdebt.
1232+
*/
1233+
if (!iocg->abs_vdebt || time_before_eq64(vtime, now->vnow)) {
12311234
blkcg_clear_delay(blkg);
12321235
return false;
12331236
}
@@ -1261,9 +1264,12 @@ static enum hrtimer_restart iocg_delay_timer_fn(struct hrtimer *timer)
12611264
{
12621265
struct ioc_gq *iocg = container_of(timer, struct ioc_gq, delay_timer);
12631266
struct ioc_now now;
1267+
unsigned long flags;
12641268

1269+
spin_lock_irqsave(&iocg->waitq.lock, flags);
12651270
ioc_now(iocg->ioc, &now);
12661271
iocg_kick_delay(iocg, &now, 0);
1272+
spin_unlock_irqrestore(&iocg->waitq.lock, flags);
12671273

12681274
return HRTIMER_NORESTART;
12691275
}
@@ -1371,14 +1377,13 @@ static void ioc_timer_fn(struct timer_list *timer)
13711377
* should have woken up in the last period and expire idle iocgs.
13721378
*/
13731379
list_for_each_entry_safe(iocg, tiocg, &ioc->active_iocgs, active_list) {
1374-
if (!waitqueue_active(&iocg->waitq) &&
1375-
!atomic64_read(&iocg->abs_vdebt) && !iocg_is_idle(iocg))
1380+
if (!waitqueue_active(&iocg->waitq) && iocg->abs_vdebt &&
1381+
!iocg_is_idle(iocg))
13761382
continue;
13771383

13781384
spin_lock(&iocg->waitq.lock);
13791385

1380-
if (waitqueue_active(&iocg->waitq) ||
1381-
atomic64_read(&iocg->abs_vdebt)) {
1386+
if (waitqueue_active(&iocg->waitq) || iocg->abs_vdebt) {
13821387
/* might be oversleeping vtime / hweight changes, kick */
13831388
iocg_kick_waitq(iocg, &now);
13841389
iocg_kick_delay(iocg, &now, 0);
@@ -1721,28 +1726,49 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio)
17211726
* tests are racy but the races aren't systemic - we only miss once
17221727
* in a while which is fine.
17231728
*/
1724-
if (!waitqueue_active(&iocg->waitq) &&
1725-
!atomic64_read(&iocg->abs_vdebt) &&
1729+
if (!waitqueue_active(&iocg->waitq) && !iocg->abs_vdebt &&
17261730
time_before_eq64(vtime + cost, now.vnow)) {
17271731
iocg_commit_bio(iocg, bio, cost);
17281732
return;
17291733
}
17301734

17311735
/*
1732-
* We're over budget. If @bio has to be issued regardless,
1733-
* remember the abs_cost instead of advancing vtime.
1734-
* iocg_kick_waitq() will pay off the debt before waking more IOs.
1736+
* We activated above but w/o any synchronization. Deactivation is
1737+
* synchronized with waitq.lock and we won't get deactivated as long
1738+
* as we're waiting or has debt, so we're good if we're activated
1739+
* here. In the unlikely case that we aren't, just issue the IO.
1740+
*/
1741+
spin_lock_irq(&iocg->waitq.lock);
1742+
1743+
if (unlikely(list_empty(&iocg->active_list))) {
1744+
spin_unlock_irq(&iocg->waitq.lock);
1745+
iocg_commit_bio(iocg, bio, cost);
1746+
return;
1747+
}
1748+
1749+
/*
1750+
* We're over budget. If @bio has to be issued regardless, remember
1751+
* the abs_cost instead of advancing vtime. iocg_kick_waitq() will pay
1752+
* off the debt before waking more IOs.
1753+
*
17351754
* This way, the debt is continuously paid off each period with the
1736-
* actual budget available to the cgroup. If we just wound vtime,
1737-
* we would incorrectly use the current hw_inuse for the entire
1738-
* amount which, for example, can lead to the cgroup staying
1739-
* blocked for a long time even with substantially raised hw_inuse.
1755+
* actual budget available to the cgroup. If we just wound vtime, we
1756+
* would incorrectly use the current hw_inuse for the entire amount
1757+
* which, for example, can lead to the cgroup staying blocked for a
1758+
* long time even with substantially raised hw_inuse.
1759+
*
1760+
* An iocg with vdebt should stay online so that the timer can keep
1761+
* deducting its vdebt and [de]activate use_delay mechanism
1762+
* accordingly. We don't want to race against the timer trying to
1763+
* clear them and leave @iocg inactive w/ dangling use_delay heavily
1764+
* penalizing the cgroup and its descendants.
17401765
*/
17411766
if (bio_issue_as_root_blkg(bio) || fatal_signal_pending(current)) {
1742-
atomic64_add(abs_cost, &iocg->abs_vdebt);
1767+
iocg->abs_vdebt += abs_cost;
17431768
if (iocg_kick_delay(iocg, &now, cost))
17441769
blkcg_schedule_throttle(rqos->q,
17451770
(bio->bi_opf & REQ_SWAP) == REQ_SWAP);
1771+
spin_unlock_irq(&iocg->waitq.lock);
17461772
return;
17471773
}
17481774

@@ -1759,20 +1785,6 @@ static void ioc_rqos_throttle(struct rq_qos *rqos, struct bio *bio)
17591785
* All waiters are on iocg->waitq and the wait states are
17601786
* synchronized using waitq.lock.
17611787
*/
1762-
spin_lock_irq(&iocg->waitq.lock);
1763-
1764-
/*
1765-
* We activated above but w/o any synchronization. Deactivation is
1766-
* synchronized with waitq.lock and we won't get deactivated as
1767-
* long as we're waiting, so we're good if we're activated here.
1768-
* In the unlikely case that we are deactivated, just issue the IO.
1769-
*/
1770-
if (unlikely(list_empty(&iocg->active_list))) {
1771-
spin_unlock_irq(&iocg->waitq.lock);
1772-
iocg_commit_bio(iocg, bio, cost);
1773-
return;
1774-
}
1775-
17761788
init_waitqueue_func_entry(&wait.wait, iocg_wake_fn);
17771789
wait.wait.private = current;
17781790
wait.bio = bio;
@@ -1804,6 +1816,7 @@ static void ioc_rqos_merge(struct rq_qos *rqos, struct request *rq,
18041816
struct ioc_now now;
18051817
u32 hw_inuse;
18061818
u64 abs_cost, cost;
1819+
unsigned long flags;
18071820

18081821
/* bypass if disabled or for root cgroup */
18091822
if (!ioc->enabled || !iocg->level)
@@ -1823,15 +1836,28 @@ static void ioc_rqos_merge(struct rq_qos *rqos, struct request *rq,
18231836
iocg->cursor = bio_end;
18241837

18251838
/*
1826-
* Charge if there's enough vtime budget and the existing request
1827-
* has cost assigned. Otherwise, account it as debt. See debt
1828-
* handling in ioc_rqos_throttle() for details.
1839+
* Charge if there's enough vtime budget and the existing request has
1840+
* cost assigned.
18291841
*/
18301842
if (rq->bio && rq->bio->bi_iocost_cost &&
1831-
time_before_eq64(atomic64_read(&iocg->vtime) + cost, now.vnow))
1843+
time_before_eq64(atomic64_read(&iocg->vtime) + cost, now.vnow)) {
18321844
iocg_commit_bio(iocg, bio, cost);
1833-
else
1834-
atomic64_add(abs_cost, &iocg->abs_vdebt);
1845+
return;
1846+
}
1847+
1848+
/*
1849+
* Otherwise, account it as debt if @iocg is online, which it should
1850+
* be for the vast majority of cases. See debt handling in
1851+
* ioc_rqos_throttle() for details.
1852+
*/
1853+
spin_lock_irqsave(&iocg->waitq.lock, flags);
1854+
if (likely(!list_empty(&iocg->active_list))) {
1855+
iocg->abs_vdebt += abs_cost;
1856+
iocg_kick_delay(iocg, &now, cost);
1857+
} else {
1858+
iocg_commit_bio(iocg, bio, cost);
1859+
}
1860+
spin_unlock_irqrestore(&iocg->waitq.lock, flags);
18351861
}
18361862

18371863
static void ioc_rqos_done_bio(struct rq_qos *rqos, struct bio *bio)
@@ -2001,7 +2027,6 @@ static void ioc_pd_init(struct blkg_policy_data *pd)
20012027
iocg->ioc = ioc;
20022028
atomic64_set(&iocg->vtime, now.vnow);
20032029
atomic64_set(&iocg->done_vtime, now.vnow);
2004-
atomic64_set(&iocg->abs_vdebt, 0);
20052030
atomic64_set(&iocg->active_period, atomic64_read(&ioc->cur_period));
20062031
INIT_LIST_HEAD(&iocg->active_list);
20072032
iocg->hweight_active = HWEIGHT_WHOLE;

tools/cgroup/iocost_monitor.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,12 @@ def __init__(self, iocg):
159159
else:
160160
self.inflight_pct = 0
161161

162-
self.debt_ms = iocg.abs_vdebt.counter.value_() / VTIME_PER_USEC / 1000
162+
# vdebt used to be an atomic64_t and is now u64, support both
163+
try:
164+
self.debt_ms = iocg.abs_vdebt.counter.value_() / VTIME_PER_USEC / 1000
165+
except:
166+
self.debt_ms = iocg.abs_vdebt.value_() / VTIME_PER_USEC / 1000
167+
163168
self.use_delay = blkg.use_delay.counter.value_()
164169
self.delay_ms = blkg.delay_nsec.counter.value_() / 1_000_000
165170

0 commit comments

Comments
 (0)