Re: [PATCH v2] ext4: do not advance i_size on a buffered write that copied nothing
From: Hengyu Liang
Date: Wed Oct 28 2026 - 00:32:22 EST
Hi Yi,
On 10/24/2026 3:12 PM, Zhang Yi wrote:
> I feel the last sentence isn't quite accurate. Dirty folio past
> EOF will be cleaned up during writeback. In mpage_process_page_bufs(),
> when lblk >= blocks, if 1) there are no blocks to map, it will
> directly call mpage_submit_folio(), which clears the dirty flag on
> the folio, and then mpage_submit_folio() will shorten the writeback
> length, so that ext4_bio_write_folio() will also clear the dirty
> flag on the corresponding bh; if 2) there are blocks to map, then
> after the required block allocation is done, it will likewise call
> mpage_submit_folio() and do similar cleanup. Am I missing something?
You are right when i_size is a multiple of the folio size. Otherwise
mpage_submit_folio() sets len = size & (len - 1), which is not 0 for a
folio that lies wholly beyond i_size. The first buffer of that folio
is still dirty and delayed, so ext4_bio_write_folio() redirties the
folio.
I checked this on the dev branch with only the copied checks of v2,
without the trim. After a failed 4 KiB write at 1 MiB into a 100-byte
file, the folio at 1 MiB was submitted with len 100 and redirtied on
every pass. It stayed dirty through three syncs, and umount printed
"data will be lost". With a 0- or 4096-byte file it was cleaned as you
describe, but the delayed block stayed reserved until eviction. I will
reword the sentence in v3.
>> + if (!copied && pos + len > old_size)
>
> Why do you keep the '!copied' condition? AFAICT, if copied is non-zero
> but less than len, the excess delalloc extent allocate by
> ext4_da_write_begin() should still be cleaned up.
Agreed. In v2 those blocks are only dropped one step later:
generic_perform_write() retries at pos + copied, that copy fails, and
->write_end() runs with copied == 0. With short copies of 2000 and 500
bytes into an uptodate folio on 1k blocks, the end result was the same,
but nothing is dropped if the loop stops before the retry, e.g. when a
fatal signal is pending. v3 will drop '!copied' and compare pos + len
with i_size after the update, as ext4_write_end() does, so a write that
copies everything does not trim.
Thanks,
Hengyu