OpenVZ / Virtuozzo kernel development (devel@openvz.org)
 help / color / mirror / Atom feed
From: Andrey Zhadchenko <andrey.zhadchenko@virtuozzo.com>
To: "Denis V. Lunev" <den@openvz.org>
Cc: devel@openvz.org
Subject: Re: [Devel] [PATCH hci-8.1 3/3] dm-ploop: fix lost BAT updates when alloc and discard share a writeback
Date: Wed, 28 Oct 2026 15:01:11 +0100	[thread overview]
Message-ID: <027fb420-d602-43c0-b7e7-d820a7f79cbb@virtuozzo.com> (raw)
In-Reply-To: <20261027163300.2166639-4-den@openvz.org>



On 10/27/26 17:32, Denis V. Lunev wrote:
> From: Denis V. Lunev <den@openvz.org>
> 
> Since "dm-ploop: fix and rework md updates" any update of a BAT page
> joins the pending index writeback of that page, whatever type it was
> prepared with. On completion ploop_advance_local_after_bat_wb() still
> applied the written page by type: a DISCARD writeback copied back only
> entries that became zero, an ALLOC one only non-zero entries.
> 
> A first write to a cluster that joined a pending DISCARD writeback was
> acknowledged with its new mapping on disk but not in the local BAT. It
> read back as zeroes, the next write to the cluster allocated another
> one, and the next writeback of the page stored that, leaking the
> cluster with the acknowledged data. A discard that joined an ALLOC
> writeback left the local BAT mapped while disk had zero, so data
> written to that cluster afterwards was lost on the next open.
> 
> Apply the changes of the pios attached to the writeback instead: the
> allocating data pios and COW pios set their entries, the discard pios
> clear theirs, other entries are left alone. The relocation on grow
> changes an entry that no pio stands for, so its writeback records
> that entry to be applied the same way. On failure the same rule as
> before frees only what this writeback allocated: a data pio may find
> its cluster already mapped by an earlier writeback.
> 
> Entries nobody changed no longer get their level reset to the top
> one; nothing needs that once the merge of the latest snapshot
> renumbers the levels itself. The PLOOP_DELAYWB branch goes with the
> old loop; nothing defines that macro.
> 
> Fixes: ddf38fa44df4 ("dm-ploop: fix and rework md updates")
> https://virtuozzo.atlassian.net/browse/VSTOR-151979
> 
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> 
> Feature: dm-ploop: ploop target driver
> ---
>   drivers/md/dm-ploop-map.c | 140 +++++++++++++++++++-------------------
>   drivers/md/dm-ploop.h     |   1 +
>   2 files changed, 71 insertions(+), 70 deletions(-)
> 
> diff --git a/drivers/md/dm-ploop-map.c b/drivers/md/dm-ploop-map.c
> index 34bf6487107f..a637df932495 100644
> --- a/drivers/md/dm-ploop-map.c
> +++ b/drivers/md/dm-ploop-map.c
> @@ -86,6 +86,7 @@ void ploop_index_wb_init(struct ploop_index_wb *piwb, struct ploop *ploop)
>   	piwb->page_id = PAGE_NR_NONE;
>   	piwb->type = PIWB_TYPE_ALLOC;
>   	piwb->flush_pio = NULL;
> +	piwb->reloc_clu = BAT_ENTRY_NONE;
>   }
>   
>   void ploop_init_pio(struct ploop *ploop, unsigned int bi_op, struct pio *pio)
> @@ -802,80 +803,85 @@ static void ploop_complete_cow(struct ploop_cow *cow, blk_status_t bi_status)
>   	kmem_cache_free(cow_cache, cow);
>   }
>   
> +static void ploop_advance_allocated(struct ploop *ploop, struct md_page *md,
> +				    map_index_t *dst_clu, u32 clu, bool success)
> +{
> +	u32 i = ploop_bat_clu_idx_in_page(clu);
> +	u32 *bat_entries = md->kmpage;
> +	u32 new_clu = READ_ONCE(dst_clu[i]);
> +
> +	if (WARN_ON_ONCE(!new_clu))
> +		return;
> +
> +	if (success) {
> +		WRITE_ONCE(bat_entries[i], new_clu);
> +		WRITE_ONCE(md->bat_levels[i], ploop_top_level(ploop));
> +	} else if (new_clu != READ_ONCE(bat_entries[i]) ||
> +		   md->bat_levels[i] < ploop_top_level(ploop)) {
> +		ploop_hole_set_bit(new_clu, ploop);
> +	}
> +}
> +
> +static void ploop_advance_discarded(struct ploop *ploop, struct md_page *md,
> +				    map_index_t *dst_clu, u32 clu, bool success)
> +{
> +	u32 i = ploop_bat_clu_idx_in_page(clu);
> +	u32 *bat_entries = md->kmpage;
> +	u8 level = md->bat_levels[i];
> +	u32 d_clu = READ_ONCE(bat_entries[i]);
> +
> +	if (!success || dst_clu[i] || d_clu == BAT_ENTRY_NONE ||
> +	    level < ploop_top_level(ploop))
> +		return;
> +
> +	WARN_ON_ONCE(ploop->nr_deltas != 1);
> +	WRITE_ONCE(bat_entries[i], BAT_ENTRY_NONE);
> +	WRITE_ONCE(md->bat_levels[i], 0);
> +	ploop_hole_set_bit(d_clu, ploop);
> +}
> +
>   /*
> - * Update local BAT copy with written indexes on success.
> - * Mark allocate clusters as holes on failure.
> + * Apply to the local BAT copy the changes made by the pios attached
> + * to @piwb, whatever type @piwb was prepared with: allocations and
> + * discards of one page may share a writeback.
>    * FIXME: a failure may mean some sectors are written, so
>    * we have to reread BAT page to check that.
>    */
>   static void ploop_advance_local_after_bat_wb(struct ploop *ploop,
>   					     struct ploop_index_wb *piwb,
> +					     struct llist_node *ll_ready_pios,
> +					     struct llist_node *ll_cow_pios,
>   					     bool success)
>   {
>   	struct md_page *md = piwb->md;
> -	u32 i, last, *bat_entries;
> -	map_index_t *dst_clu, off;
> +	map_index_t *dst_clu;
>   	unsigned long flags;
>   	LIST_HEAD(list);
>   	struct llist_node *wait_llist_pending;
> +	struct ploop_cow *cow;
>   	struct pio *pio;
>   	struct llist_node *pos, *t;
>   	BUG_ON(!md);
> -	bat_entries = md->kmpage;
> -
> -	/* Absolute number of first index in page (negative for page#0) */
> -	off = piwb->page_id * PAGE_SIZE / sizeof(map_index_t);
> -	off -= PLOOP_MAP_OFFSET;
>   
>   	spin_lock_irqsave(&ploop->bat_lock, flags);
> -	/* Last and first index in copied page */
> -	last = READ_ONCE(ploop->nr_bat_entries) - off;
> -	if (last > PAGE_SIZE / sizeof(map_index_t))
> -		last = PAGE_SIZE / sizeof(map_index_t);
> -	i = 0;
> -	if (!piwb->page_id)
> -		i = PLOOP_MAP_OFFSET;
> -
>   	dst_clu = kmap_local_page(piwb->bat_page);
>   
>   	/* holes bit map requires bat_lock */
>   	spin_lock(&md->md_lock);
> -#ifdef PLOOP_DELAYWB
> -	if (piwb->type == PIWB_TYPE_ALLOC)
> -		goto skip_apply;
> -#endif
> -	for (; i < last; i++) {
> -		if (piwb->type == PIWB_TYPE_DISCARD) {
> -			/* discard completed */
> -			u8 level = md->bat_levels[i];
> -			u32 d_clu = READ_ONCE(bat_entries[i]);
> -
> -			if (success && !dst_clu[i] &&
> -			    (!(d_clu == BAT_ENTRY_NONE ||
> -			    level < ploop_top_level(ploop)))) {
> -				WARN_ON_ONCE(ploop->nr_deltas != 1);
> -				WRITE_ONCE(bat_entries[i], BAT_ENTRY_NONE);
> -				WRITE_ONCE(md->bat_levels[i], 0);
> -				ploop_hole_set_bit(d_clu, ploop);
> -			}
> -
> -			continue;
> -		}
> -
> -		if (!dst_clu[i])
> -			continue;
> -
> -		if (success) {
> -			WRITE_ONCE(bat_entries[i], READ_ONCE(dst_clu[i]));
> -			WRITE_ONCE(md->bat_levels[i], ploop_top_level(ploop));
> -		} else if (dst_clu[i] != bat_entries[i] ||
> -			   md->bat_levels[i] < ploop_top_level(ploop)) {
> -			ploop_hole_set_bit(dst_clu[i], ploop);
> -		}
> +	llist_for_each(pos, ll_ready_pios) {
> +		pio = list_entry((struct list_head *)pos, typeof(*pio), list);
> +		if (pio->wants_discard_index_cleanup)
> +			ploop_advance_discarded(ploop, md, dst_clu, pio->clu, success);
> +		else if (pio->is_data_alloc)
> +			ploop_advance_allocated(ploop, md, dst_clu, pio->clu, success);
>   	}
> -#ifdef PLOOP_DELAYWB
> -skip_apply:
> -#endif

Maybe even drop PLOOP_DELAYWB as a separate patch and in other places 
too? Looks like it is dead anyway. And this remove makes it even more 
inconsistent.

> +	llist_for_each(pos, ll_cow_pios) {
> +		pio = list_entry((struct list_head *)pos, typeof(*pio), list);
> +		cow = pio->endio_cb_data;
> +		ploop_advance_allocated(ploop, md, dst_clu, cow->cow_pio->clu, success);
> +	}
> +	if (piwb->reloc_clu != BAT_ENTRY_NONE)
> +		ploop_advance_allocated(ploop, md, dst_clu, piwb->reloc_clu, success);
>   
>   	WARN_ON_ONCE(!test_bit(MD_WRITEBACK, &md->status));
>   
> @@ -948,24 +954,6 @@ static void ploop_bat_write_finish(struct pio *pio, void *piwb_ptr,
>   	struct llist_node *ll_cow_pios;
>   	struct llist_node *ll_ready_pios;
>   
> -	if (!bi_status) {
> -		/*
> -		 * Success: now update local BAT copy. We could do this
> -		 * from our delayed work, but we want to publish new
> -		 * mapping in the fastest way. This must be done before
> -		 * data bios completion, since right after we complete
> -		 * a bio, subsequent read wants to see written data
> -		 * (ploop_map() wants to see not zero bat_entries[.]).
> -		 */
> -		ploop_advance_local_after_bat_wb(ploop, piwb, true);
> -	} else {
> -		/*
> -		 * Index wb failed. Mark clusters as unallocated again.
> -		 * piwb->count is zero, so all data writers compeleted.
> -		 */
> -		ploop_advance_local_after_bat_wb(ploop, piwb, false);
> -	}
> -
>   	spin_lock_irqsave(&piwb->lock, flags);
>   	ll_ready_pios = llist_del_all(&piwb->llready_data_pios);
>   	if (bi_status)
> @@ -973,6 +961,17 @@ static void ploop_bat_write_finish(struct pio *pio, void *piwb_ptr,
>   	spin_unlock_irqrestore(&piwb->lock, flags);
>   	ll_cow_pios = llist_reverse_order(llist_del_all(&piwb->cow_llist));
>   
> +	/*
> +	 * On success publish the new mapping before the data bios
> +	 * complete, since right after we complete a bio, subsequent
> +	 * read wants to see written data (ploop_map() wants to see
> +	 * not zero bat_entries[.]). On failure mark the clusters
> +	 * allocated for this writeback as holes again: piwb->count
> +	 * is zero, so all data writers completed.
> +	 */
> +	ploop_advance_local_after_bat_wb(ploop, piwb, ll_ready_pios,
> +					 ll_cow_pios, !bi_status);
> +
>   	/*
>   	 * End pending data bios.
>   	 */
> @@ -2737,6 +2736,7 @@ int ploop_prepare_reloc_index_wb(struct ploop *ploop,
>   		err = ploop_alloc_cluster(ploop, piwb, clu, dst_clu, file);
>   		if (err)
>   			goto out_reset;
> +		piwb->reloc_clu = clu;
>   	}
>   
>   	*ret_md = md;
> diff --git a/drivers/md/dm-ploop.h b/drivers/md/dm-ploop.h
> index 23662943d56a..ce834df6df55 100644
> --- a/drivers/md/dm-ploop.h
> +++ b/drivers/md/dm-ploop.h
> @@ -108,6 +108,7 @@ struct ploop_index_wb {
>   	blk_status_t bi_status;
>   	u32 page_id;
>   	struct bio_vec aux_bvec;
> +	c;
>   	struct pio *flush_pio;
>   };
>   

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

  reply	other threads:[~2026-10-28 14:02 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27 16:32 [Devel] [PATCH hci-8.1 0/3] dm-ploop: fix acknowledged writes lost when discard and allocation share a BAT writeback Denis V. Lunev
2026-10-27 16:32 ` [Devel] [PATCH hci-8.1 1/3] dm-ploop: fix freeing the wrong clusters when a BAT writeback fails Denis V. Lunev
2026-10-27 16:32 ` [Devel] [PATCH hci-8.1 2/3] dm-ploop: renumber BAT levels when the latest snapshot is merged Denis V. Lunev
2026-10-28 13:40   ` Andrey Zhadchenko
2026-10-27 16:32 ` [Devel] [PATCH hci-8.1 3/3] dm-ploop: fix lost BAT updates when alloc and discard share a writeback Denis V. Lunev
2026-10-28 14:01   ` Andrey Zhadchenko [this message]
2026-10-28 14:02 ` [Devel] [PATCH hci-8.1 0/3] dm-ploop: fix acknowledged writes lost when discard and allocation share a BAT writeback Andrey Zhadchenko

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=027fb420-d602-43c0-b7e7-d820a7f79cbb@virtuozzo.com \
    --to=andrey.zhadchenko@virtuozzo.com \
    --cc=den@openvz.org \
    --cc=devel@openvz.org \
    /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