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.
next prev parent 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®