OpenVZ / Virtuozzo kernel development (devel@openvz.org)
 help / color / mirror / Atom feed
From: no-reply@sashiko.virtuozzo.com
To: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>,
	Konstantin Khorenko <khorenko@virtuozzo.com>
Cc: devel@openvz.org
Subject: Re: [Devel] [PATCH hci-8.1 v5 6/6] block/blk-cbt: account a write request also when dm starts it
Date: Tue, 27 Oct 2026 14:33:49 +0000	[thread overview]
Message-ID: <010701a17dc81f1a-33ed06f7-e3d1-4ff1-80e5-46942a316c4d-000000@eu-central-1.amazonses.com> (raw)
In-Reply-To: <20261027-ptikhomirov-vstor-149268-cbt-account-at-rq-start-v5-6-88dccc74b48b@virtuozzo.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] block/cbt: cbt_set_present() double-reads cbt->map[idx] via CBT_PAGE() locklessly
- [Low] block/cbt: blk_cbt_add() queues page allocation under lock for every write bio
--

commit 14429eee1ca32a8d25d6ddbe3a350777fa35d5cc
Author: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>

block/blk-cbt: account a write request also when dm starts it

This patch makes request based dm call a new blk_cbt_rq_start() hook from
dm_mq_queue_rq(), so a write is accounted in CBT again when dm starts it.
The hook sets bits only in bitmap pages that already exist. Missing pages
are queued for allocation to kblockd, and dm returns BLK_STS_RESOURCE so
the request is dispatched again later.

> diff --git a/block/blk-cbt.c b/block/blk-cbt.c
> --- a/block/blk-cbt.c
> +++ b/block/blk-cbt.c

[ ... ]

> +static void cbt_queue_alloc(struct cbt_info *cbt, unsigned long idx)
> +{
> +	unsigned long flags;
> +
> +	set_bit(idx, cbt->missing);
> +	spin_lock_irqsave(&cbt->lock, flags);
> +	if (test_bit(CBT_DEAD, &cbt->flags)) {
> +		spin_unlock_irqrestore(&cbt->lock, flags);
> +		return;
> +	}
> +	cbt->count++;
> +	spin_unlock_irqrestore(&cbt->lock, flags);
> +
> +	/* The queued work drops the reference, or drop it if already queued */
> +	if (!kblockd_schedule_work(&cbt->alloc_work))
> +		cbt_put(cbt);
> +}

[ ... ]

> @@ -271,6 +352,8 @@ static bool blk_cbt_add(struct cbt_info *cbt, blkcnt_t start, blkcnt_t len,
>  		__blk_cbt_set(cbt, start, len, 1, NULL, NULL);
>  		return false;
>  	}
> +	/* Queue the allocation early, so blk_cbt_rq_start() finds the pages */
> +	cbt_queue_alloc_pages(cbt, start, len);
>  	ex = get_cpu_ptr(cbt->cache);

[Severity: Low]
This isn't a correctness problem, but is the per-bio cost here intended?

blk_cbt_add() now calls cbt_queue_alloc_pages() for every write bio before
the per-CPU extent cache fast path. While a bitmap page is still missing,
each such bio goes through cbt_queue_alloc():

    set_bit(idx, cbt->missing);
    spin_lock_irqsave(&cbt->lock, flags);
    cbt->count++;
    ...
    if (!kblockd_schedule_work(&cbt->alloc_work))
        cbt_put(cbt);

When the work is already pending, kblockd_schedule_work() returns 0 and
cbt_put() takes cbt->lock a second time.

Nothing dedupes the requests, for example by testing the bit with
test_and_set_bit() on cbt->missing before taking the lock. The window lasts
until the kblockd work installs the page, and that can be delayed because
the allocation is GFP_NOIO. Would it make sense to skip the locked
reference and queue_work() when the bit is already set?

[ ... ]

> +static bool cbt_set_present(struct cbt_info *cbt, blkcnt_t start, blkcnt_t len)
> +{

[ ... ]

> +	while (block < end) {
> +		unsigned long idx = block / BITS_PER_PAGE;
> +		unsigned long off = block & (BITS_PER_PAGE - 1);
> +		unsigned long n = min_t(unsigned long, BITS_PER_PAGE - off,
> +					end - block);
> +		struct page *page = CBT_PAGE(cbt, idx);
> +
> +		if (page) {
> +			local_irq_save(flags);
> +			spin_lock_page(page);
> +			set_bits(page_address(page), off, n, 1);
> +			unlock_page(page);
> +			local_irq_restore(flags);

[Severity: High]
Can this read of the page pointer race with __blk_cbt_set() and hand an
invalid page pointer to spin_lock_page()?

CBT_PAGE(cbt, idx) expands to:

    (cbt->map[idx] == CBT_PAGE_MISSED ? NULL : cbt->map[idx])

so it loads the slot twice, with no READ_ONCE() and no cbt->lock held. The
compiler is also free to reload it.

Concurrently, __blk_cbt_set() with pages_missed set (reached from the
BLKCBTSTART / CBT_SNAP_CREATE flush path) does this under cbt->lock, only
for a slot that was NULL:

    if (!installed)
        cbt->map[idx] = CBT_PAGE_MISSED;

One possible interleaving:

    reader: first load of cbt->map[idx] sees NULL, so not MISSED
    writer: stores 0x1 into cbt->map[idx]
    reader: second load returns 0x1

page is then the bogus non-NULL value 0x1, and trylock_page() and
page_address() in cbt_set_present() dereference it. cbt_set_present() never
takes cbt->lock, so nothing excludes the writer.

This runs from blk_cbt_rq_start() in the dm_mq_queue_rq() dispatch path and
would hit ordinary writers, during exactly the backup-point operations this
patch is meant to cover. Should the slot be read once into a local variable
with READ_ONCE() and then compared against CBT_PAGE_MISSED, or should the
lookup be done under cbt->lock?

[ ... ]

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261027-ptikhomirov-vstor-149268-cbt-account-at-rq-start-v5-0-88dccc74b48b@virtuozzo.com?part=6

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

      reply	other threads:[~2026-10-27 14:35 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 14:23 [Devel] [PATCH hci-8.1 v5 0/6] block/blk-cbt: don't lose writes held across a dm suspend Pavel Tikhomirov
2026-10-27 14:23 ` [Devel] [PATCH hci-8.1 v5 1/6] block/blk-cbt: shift the block size as unsigned Pavel Tikhomirov
2026-10-27 14:27   ` no-reply
2026-10-27 14:23 ` [Devel] [PATCH hci-8.1 v5 2/6] block/blk-throttle: account a throttled write in CBT Pavel Tikhomirov
2026-10-27 14:27   ` no-reply
2026-10-27 14:23 ` [Devel] [PATCH hci-8.1 v5 3/6] block/blk-cbt: don't overwrite a map page with CBT_PAGE_MISSED Pavel Tikhomirov
2026-10-27 14:30   ` no-reply
2026-10-27 14:23 ` [Devel] [PATCH hci-8.1 v5 4/6] block/blk-cbt: walk cbt_list under SRCU Pavel Tikhomirov
2026-10-27 14:29   ` no-reply
2026-10-27 14:23 ` [Devel] [PATCH hci-8.1 v5 5/6] block/blk-cbt: don't sleep in CBT for a REQ_NOWAIT write Pavel Tikhomirov
2026-10-27 14:33   ` no-reply
2026-10-27 14:23 ` [Devel] [PATCH hci-8.1 v5 6/6] block/blk-cbt: account a write request also when dm starts it Pavel Tikhomirov
2026-10-27 14:33   ` no-reply [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=010701a17dc81f1a-33ed06f7-e3d1-4ff1-80e5-46942a316c4d-000000@eu-central-1.amazonses.com \
    --to=no-reply@sashiko.virtuozzo.com \
    --cc=devel@openvz.org \
    --cc=khorenko@virtuozzo.com \
    --cc=ptikhomirov@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox