mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Zhang Yi <yizhang089@gmail.com>
To: Joanne Koong <joannelkoong@gmail.com>
Cc: linux-mm@kvack.org, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-ext4@vger.kernel.org,
	akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org,
	liam@infradead.org, vbabka@kernel.org, rppt@kernel.org,
	surenb@google.com, mhocko@suse.com, hughd@google.com,
	baolin.wang@linux.alibaba.com, willy@infradead.org, jack@suse.cz,
	ziy@nvidia.com, bfoster@redhat.com, djwong@kernel.org,
	yi.zhang@huawei.com, yangerkun@huawei.com,
	chengzhihao1@huawei.com, wangkefeng.wang@huawei.com,
	yukuai@fnnas.com, Zhang Yi <yi.zhang@huaweicloud.com>
Subject: Re: [RFC PATCH] mm/truncate: fix data loss when splitting fails in truncate_inode_partial_folio()
Date: Sat, 5 Sep 2026 17:46:32 +0800	[thread overview]
Message-ID: <5555e6c7-5167-45ab-b1f0-f9a589d67f07@gmail.com> (raw)
In-Reply-To: <CAJnrk1bQYUe6+1ryyJur5EEnZYrC+_5AYsy=OWzVRgD4202y1g@mail.gmail.com>

On 9/5/2026 1:33 AM, Joanne Koong wrote:
> On Thu, Sep 3, 2026 at 11:27 PM Zhang Yi <yi.zhang@huaweicloud.com> wrote:
>>
>> On 9/4/2026 3:01 AM, Joanne Koong wrote:
>>>>          /*
>>>> @@ -259,6 +265,10 @@ bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end)
>>>>                   * for shmem truncate
>>>>                   */
>>>>                  struct folio *folio2;
>>>> +               bool tail_isolated = true;
>>>> +
>>>> +               if (end)
>>>> +                       *end = (pos + offset + length) >> PAGE_SHIFT;
>>>
>>> If I'm understanding it correctly, based on how
>>> truncate_inode_pages_range() uses the end value (eg the "while (index
>>> < end)" loop condition and the find_get_entries(..., end - 1, ...)),
>>> end needs to point to the start of the folio if the tail folio from
>>> the split is a large folio, in order to exclude that folio from then
>>> being truncated. But with the (pos + offset + length) >> PAGE_SHIFT
>>> calculation here, does that result in some cases in it pointing to the
>>> middle of a large folio? Maybe some logic is needed to make sure that
>>> it points to the start?
>>>
>>
>> Hi Joanne,
>>
>> Thanks for your careful review! I don't think that case can actually
>> occur. Let me walk through the scenarios where the
>> (pos + offset + length) >> PAGE_SHIFT calculation is kept as the final
>> end value:
> 
> Hi Yi,
> 
> Thanks for your reply and for walking through the logic and explaining it.
> 
>>
>> 1) offset + length == size:
>>     The truncate range aligns exactly with the folio boundary, so
>>     (pos + offset + length) >> PAGE_SHIFT points to the start of the next
>>     folio, not the middle of one.
>>
>> 2) !folio_try_get(folio2):
>>     The folio at that position has already been freed or is being freed,
>>     so there is no folio in the page cache at that location. The
>>     subsequent find_get_entries() won't find anything there.
>>
>> 3) !folio_test_large(folio2):
>>     folio2 is no longer large, likely split to order-0 by a concurrent
>>     operation. For an order-0 folio, the page index and folio index are
>>     the same, so the calculation is correct.
>>
>> 4) folio2 becomes stale:
>>     The same to case 2), folio2 is removed from the address space, so
>>     there is no large folio straddling the boundary that needs
>>     protection. The caller won't get folio from here through
>>     find_get_entries(). Using the page index is safe here.
>>
>> If a large folio straddles the boundary at (offset + length), we will
>> successfully get a reference via folio_try_get(folio2) and
>> folio_test_large(folio2) will be true. In that case, if the split fails
>> (cannot lock or split operation fails), we set tail_isolated to false
>> and set *end = folio2->index to point to the start of that large folio
>> since tail_isolated.
> 
> The case I have in mind is the case where the 2nd split succeeds
> (returns 0) and tail_isolated will not be set to false, and *end still
> gets returned back to the caller as the original "(pos + offset +
> length) >> PAGE_SHIFT" calculation. I don't think it's guaranteed that
> the split will create a folio that starts at that page.
> 
> The example I'm thinking about is a 64k folio being truncated at
> offset 0 to 36k on a 16k blocksize filesystem with 4k pages:
> *end = (pos + offset + length) >> PAGE_SHIFT = 36k >> PAGE_SHIFT = page index 9
> start: [0 - 15]
> after 1st split: [0 - 3] [4 - 7] [8 - 15]
> split_at2 = 36k / PAGE_SIZE = 9
> 2nd split will try splitting [8 - 15] at split_at2
> after 2nd split: [8 - 11] [12 - 15]
> 
> in the truncate_inode_pages_range() logic, start = 0, end = 9, so
> find_get_entries(..., end - 1 (= 8) ...) returns the [8 - 11] folio
> and truncate_inode_folio() will drop all 4 of those pages (including
> 36k to 48k which might have dirty data).
> 
> Do you think this makes sense or am I missing something?
> 

Ha, indeed, thanks for pointing this out.  The second split only
isolates the tail when the boundary is min_order-aligned, otherwise
the straddler still remains and the valid tail gets dropped.  So this
problem is not only triggered when the second split fails and I've
reproduced this scenario. :)

>>
>> The page index from this setting is only kept when no large folio exists
>> at the boundary, which makes it safe to use. What is particularly
>> noteworthy is that for cases 2 and 4 above, aside from setting it to
>> (pos + offset + length) >> PAGE_SHIFT, there does not seem to be any
>> better alternative.
> 
> I think if we just rounded down *end by the min order (eg *end =
> round_down((pos + offset + length) >> PAGE_SHIFT, 1UL << min_order);),
> that would ensure end is always on a folio boundary and can't be
> inside a folio.
> 

Yeah, rounding *end down to a min_order boundary looks good to me.
I'll update the patch accordingly.

Thanks,
Yi.

  reply	other threads:[~2026-09-05  9:46 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-03 11:50 Zhang Yi
2026-09-03 19:01 ` Joanne Koong
2026-09-04  6:27   ` Zhang Yi
2026-09-04 17:33     ` Joanne Koong
2026-09-05  9:46       ` Zhang Yi [this message]
2026-09-04  9:06 ` Zhang Yi
2026-09-04 18:03 ` Brian Foster
2026-09-05  9:58   ` Zhang Yi
2026-09-08 12:20     ` Zhang Yi
2026-09-04 19:31 ` Zi Yan
2026-09-05  9:15   ` Zhang Yi
2026-09-05 12:41     ` Zi Yan
2026-09-07  3:26       ` Zhang Yi

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=5555e6c7-5167-45ab-b1f0-f9a589d67f07@gmail.com \
    --to=yizhang089@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=baolin.wang@linux.alibaba.com \
    --cc=bfoster@redhat.com \
    --cc=chengzhihao1@huawei.com \
    --cc=david@kernel.org \
    --cc=djwong@kernel.org \
    --cc=hughd@google.com \
    --cc=jack@suse.cz \
    --cc=joannelkoong@gmail.com \
    --cc=liam@infradead.org \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mhocko@suse.com \
    --cc=rppt@kernel.org \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    --cc=wangkefeng.wang@huawei.com \
    --cc=willy@infradead.org \
    --cc=yangerkun@huawei.com \
    --cc=yi.zhang@huawei.com \
    --cc=yi.zhang@huaweicloud.com \
    --cc=yukuai@fnnas.com \
    --cc=ziy@nvidia.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

all inboxes | Powered by JetHome®