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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.