From: Konstantin Khorenko <khorenko@virtuozzo.com>
To: Liu Kui <kui.liu@virtuozzo.com>, Alexey Kuznetsov <kuznet@virtuozzo.com>
Cc: devel@openvz.org, azaitsev@virtuozzo.com
Subject: Re: [Devel] [PATCH hci-8.0] fs/fuse: fix writectr imbalance when failing writeback of invalidated file
Date: Mon, 21 Sep 2026 17:55:12 +0200 [thread overview]
Message-ID: <eb77a898-57f7-4f64-9bf6-724c2df2842c@virtuozzo.com> (raw)
In-Reply-To: <179000337661.2953935.5887806686603853170.b4-review@b4>
Actors: T1 opens and later closes the file through ff B; T2 writes and truncates through ff C; T3 does an O_DIRECT read into C's mmap; flusher is background writeback; fused is the userspace daemon; kworker runs fuse_inval_files_work(). Inode I,
page P, vstorage mount (close_wait=1, writeback_cache=1).
Part 1. Setup: a stale open makes B the head of write_files
step who what happens state after the step
---- ------- ---------------------------------------------------------- ------------------------------------
1 T1 open(f) -> fuse_send_open(): FUSE_OPEN for B sent to fused, B: open_seq = N, no reply yet
waiting for the reply
2 fused writes FUSE_NOTIFY_INVAL_FILES(I) to /dev/fuse fi->inval_files_seq = N+1
-> fuse_invalidate_files(): every ff on fi->rw_files (old ones):
set_bit(FAIL_IMMEDIATELY) on every ff in fi->rw_files FAIL_IMMEDIATELY
set_bit(FUSE_I_INVAL_FILES, &fi->state) INVAL bit set
queue_delayed_work(inval_files_work)
3 kworker fuse_inval_files_work(): page cache of I is empty
truncate_pagecache_range(I, 0, -1) INVAL bit cleared
clear_bit(FUSE_I_INVAL_FILES)
4 T2 open(f) -> FUSE_OPEN for C, fused replies C: open_seq = N+2 > N+1, not stale
fuse_finish_open() -> fuse_link_write_file(): list_add fi->write_files = [C, ...]
fuse_link_rw_file(): open_seq >= inval_files_seq, fine
5 fused replies to FUSE_OPEN for B (step 1)
T1 fuse_finish_open() -> fuse_link_write_file(): list_add fi->write_files = [B, C, ...]
fuse_link_rw_file(): B->open_seq N < inval_files_seq N+1 B: FAIL_IMMEDIATELY
-> set_bit(FUSE_S_FAIL_IMMEDIATELY, &B->ff_state)
B->count = 1 (the open only)
From here on __fuse_write_file_get() for I returns B: it is first in the list and it is "dead". This is old behaviour, unrelated to the patch.
Part 2. Concurrent part: primary waits in the queue, aux attaches to it, out_free
One column per thread, time runs top to bottom. Rightmost column is shared state.
T2 (ff C) T3 (ff C, mmap) flusher state
------------------------------ ---------------------------- ----------------------------------- ----------------------------
6. mmap(C); writes into P P dirty, PTE writable
page_mkwrite() ok, C not FAIL
7. read(X, addr(P), O_DIRECT)
GUP: FOLL_WRITE, P already
dirty and writable, pinned
request sent to disk, waiting
8. ftruncate(C)
fuse_do_setattr():
inode_lock(I)
fuse_set_nowrite():
writectr += FUSE_NOWRITE writectr = FUSE_NOWRITE
wait writectr == NOWRITE (nothing in flight, returns)
FUSE_SETATTR -> fused,
waiting for the reply
9. fuse_writepages(I)
fill(P):
INVAL bit clear -> proceed
data->ff = write_file_get() = B B->count = 2
clear_page_dirty_for_io(P) PTE of P write-protected
wpa1 = args_setup(P, B)
fuse_file_get(B) B->count = 3
tree_insert(wpa1) P in fi->writepages
fuse_writepages_send():
list_add_tail(queued_writes) wpa1 queued
fuse_flush_writepages():
writectr < 0 -> nothing
end_page_writeback(P)
fuse_release_ff(B) B->count = 2
(before the patch: shoot on
sight, wpa1 completed at
once, never queued)
10. disk replied, DIO read
completes:
unpin_user_pages_dirty_lock()
-> set_page_dirty(P) P dirty again,
no page_mkwrite, hence no fuse_page_is_writeback(P)
fuse_wait_on_page_writeback = true (wpa1 in the tree)
11. fuse_writepages(I), next pass
fill(P):
data->ff = B B->count = 3
wpa2 = args_setup(P, B)
fuse_file_get(B) B->count = 4
fuse_writepage_add(wpa2, P):
insert_writeback finds wpa1
no P among wpa1->next
wpa1->next = wpa2 wpa2 = aux of wpa1
return false
folio_end_writeback(P)
fuse_release_ff(B) B->count = 3
(1 open + wpa1 + wpa2)
12. fused replied to SETATTR
fuse_do_setattr():
spin_lock(fi->lock)
__fuse_release_nowrite():
writectr = 0
fuse_flush_writepages():
fuse_send_writepage(wpa1):
writectr++ writectr = 1
fail_immediately(B) -> true <== new check from the patch
mapping_set_error(-EIO)
goto out_free:
writectr-- writectr = 0
rb_erase(wpa1)
fuse_writepage_finish(wpa1):
close_wait -> __fuse_file_put(B) B->count = 2
spin_unlock
for aux = wpa1->next (wpa2):
fuse_writepage_finish_stat(wpa2)
fuse_writepage_free(wpa2):
close_wait -> put is NOT done <== the leak B->count = 2, should be 1
kfree(wpa2)
fuse_writepage_free(wpa1): no put (close_wait), kfree
spin_unlock(fi->lock)
inode_unlock(I)
Part 3. Outcome
step who what happens result
---- ---- -------------------------------------------------------------- ---------------------------------
13 T1 close(B) -> fuse_release():
write_inode_now(I, 1) no dirty pages left
list_del_init(&B->write_entry)
wait_event(fi->page_waitq,
refcount_read(&B->count) == 1) B->count = 2 forever,
no timeout, nobody to wake it:
close() never returns
i think the leak is not that probable and i can commit the current patch as is
and we can go production without fixing that leak.
What do you think?
--
Best regards,
Konstantin Khorenko,
Virtuozzo Linux Kernel Team
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
next prev parent reply other threads:[~2026-09-21 15:56 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 10:25 Liu Kui
2026-09-19 11:14 ` Alexey Kuznetsov
2026-09-21 15:09 ` Konstantin Khorenko
2026-09-21 15:55 ` Konstantin Khorenko [this message]
2026-09-21 15:58 ` Konstantin Khorenko
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=eb77a898-57f7-4f64-9bf6-724c2df2842c@virtuozzo.com \
--to=khorenko@virtuozzo.com \
--cc=azaitsev@virtuozzo.com \
--cc=devel@openvz.org \
--cc=kui.liu@virtuozzo.com \
--cc=kuznet@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