fuse: close file synchronously
cover fuse: close file synchronously
Hi,
There is a long-standing demand for syncronous behaviour of fuse_release:
http://sourceforge.net/mailarchive/message.php?msg_id=19343889
http://sourceforge.net/mailarchive/message.php?msg_id=29814693
A few months ago Avati and me explained why such a feature would be useful:
http://sourceforge.net/mailarchive/message.php?msg_id=29889055
http://sourceforge.net/mailarchive/message.php?msg_id=29867423
In short, the problem is that fuse_release (that's called on last user close(2)) sends FUSE_RELEASE to userspace and returns without waiting for ACK from userspace. Consequently, there is a gap when user regards the file released while userspace fuse is still working on it. An attempt to access the file from another node leads to complicated synchronization problems because the first node still "holds" the file.
The patch-set resolves the problem by making fuse_release synchronous:
wait for ACK from userspace for FUSE_RELEASE if the feature is ON.
To keep single-threaded userspace implementations happy the patch-set ensures that by the time fuse_release_common calls fuse_file_put, no more in-flight I/O exists. Asynchronous fuse callbacks (like fuse_readpages_end) cannot trigger FUSE_RELEASE anymore. Hence, we'll never block in contexts other than close().
Thanks,
Maxim
---
Maxim Patlasov (5):
fuse: add close_wait flag to fuse_conn
fuse: cosmetic rework of fuse_send_readpages
fuse: wait for end of IO on release
fuse: enable close_wait feature
fuse: fix synchronous case of fuse_file_put()
fs/fuse/file.c | 82 ++++++++++++++++++++++++++++++++++++++------- fs/fuse/fuse_i.h | 3 ++ fs/fuse/inode.c | 5 ++- include/uapi/linux/fuse.h | 7 +++- 4 files changed, 82 insertions(+), 15 deletions(-)
Signature · 1 line
Signature
PATCH 1/5 fuse: add close_wait flag to fuse_conn
The feature will be governed by fc->close_wait. Userspace can enable it in the same way as auto_inval_data or any other kernel fuse capability.
--- fs/fuse/fuse_i.h | 3 +++ fs/fuse/inode.c | 5 ++++- include/uapi/linux/fuse.h | 7 ++++++- 3 files changed, 13 insertions(+), 2 deletions(-)
fs/fuse/fuse_i.h +3 -0
@@ -487,6 +487,9 @@ struct fuse_conn {/** Use enhanced/automatic page cache invalidation. */ unsigned auto_inval_data:1; +/** Wait for response from daemon on close */ + unsigned close_wait:1; +/** The number of requests waiting for completion */ atomic_t num_waiting;
fs/fuse/inode.c +4 -1
@@ -859,6 +859,8 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req) fc->dont_mask =1 ;if (arg->flags &FUSE_AUTO_INVAL_DATA ) fc->auto_inval_data =1 ; +if (arg->flags &FUSE_CLOSE_WAIT ) + fc->close_wait =1 ; }else { ra_pages = fc->max_read /PAGE_CACHE_SIZE ; fc->no_lock =1 ;
@@ -885,7 +887,8 @@ static void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req) arg->flags |=FUSE_ASYNC_READ |FUSE_POSIX_LOCKS |FUSE_ATOMIC_O_TRUNC |FUSE_EXPORT_SUPPORT |FUSE_BIG_WRITES |FUSE_DONT_MASK |FUSE_SPLICE_WRITE |FUSE_SPLICE_MOVE |FUSE_SPLICE_READ | -FUSE_FLOCK_LOCKS |FUSE_IOCTL_DIR |FUSE_AUTO_INVAL_DATA ; +FUSE_FLOCK_LOCKS |FUSE_IOCTL_DIR |FUSE_AUTO_INVAL_DATA | +FUSE_CLOSE_WAIT ; req->in.h.opcode =FUSE_INIT ; req->in.numargs =1 ; req->in.args[0 ].size =sizeof (*arg);
include/uapi/linux/fuse.h +6 -1
@@ -60,6 +60,9 @@* * 7.20 * - add FUSE_AUTO_INVAL_DATA +* +* 7.21 +* - add FUSE_CLOSE_WAIT */ #ifndef _LINUX_FUSE_H
@@ -91,7 +94,7 @@#define FUSE_KERNEL_VERSION 7/** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 20 +#define FUSE_KERNEL_MINOR_VERSION 21/** The node ID of the root inode */ #define FUSE_ROOT_ID 1
@@ -179,6 +182,7 @@ struct fuse_file_lock {* FUSE_FLOCK_LOCKS: remote locking for BSD style file locks * FUSE_HAS_IOCTL_DIR: kernel supports ioctl on directories * FUSE_AUTO_INVAL_DATA: automatically invalidate cached pages +* FUSE_CLOSE_WAIT: wait for response from daemon on close */ #define FUSE_ASYNC_READ (1 << 0)#define FUSE_POSIX_LOCKS (1 << 1)
@@ -193,6 +197,7 @@ struct fuse_file_lock {#define FUSE_FLOCK_LOCKS (1 << 10)#define FUSE_HAS_IOCTL_DIR (1 << 11)#define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_CLOSE_WAIT (1 << 14)/** * CUSE INIT request/reply flags
PATCH 2/5 fuse: cosmetic rework of fuse_send_readpages
In reply to Maxim PatlasovThe patch change arguments of fuse_send_readpages to give it access to inode (will be used in the next patch of patch-set). The change is cosmetic, no logic changed.
--- fs/fuse/file.c | 20 +++++++++++--------- 1 files changed, 11 insertions(+), 9 deletions(-)
fs/fuse/file.c +11 -9
@@ -614,8 +614,16 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)fuse_file_put (req->ff, false); } -static void fuse_send_readpages (struct fuse_req *req,struct file *file) +struct fuse_fill_data { +struct fuse_req *req; +struct file *file; +struct inode *inode; +}; + +static void fuse_send_readpages (struct fuse_fill_data *data) { +struct fuse_req *req = data->req; +struct file *file = data->file;struct fuse_file *ff = file->private_data;struct fuse_conn *fc = ff->fc;loff_t pos =page_offset (req->pages[0 ]);
@@ -637,12 +645,6 @@ static void fuse_send_readpages(struct fuse_req *req, struct file *file) } } -struct fuse_fill_data { -struct fuse_req *req; -struct file *file; -struct inode *inode; -}; -static int fuse_readpages_fill (void *_data,struct page *page) {struct fuse_fill_data *data = _data;
@@ -656,7 +658,7 @@ static int fuse_readpages_fill(void *_data, struct page *page) (req->num_pages == FUSE_MAX_PAGES_PER_REQ || (req->num_pages + 1) * PAGE_CACHE_SIZE > fc->max_read || req->pages[req->num_pages - 1]->index + 1 != page->index)) { -fuse_send_readpages (req, data->file); +fuse_send_readpages (data); data->req = req =fuse_get_req (fc);if (IS_ERR (req)) {unlock_page (page);
@@ -691,7 +693,7 @@ static int fuse_readpages(struct file *file, struct address_space *mapping, err =read_cache_pages (mapping, pages, fuse_readpages_fill, &data);if (!err) {if (data.req->num_pages) -fuse_send_readpages (data.req, file); +fuse_send_readpages (&data);else fuse_put_request (fc, data.req); }
PATCH 3/5 fuse: wait for end of IO on release
In reply to Maxim PatlasovThere are two types of I/O activity that can be "in progress" at the time of fuse_release() execution: asynchronous read-ahead and write-back. The patch ensures that they are completed before fuse_release_common sends FUSE_RELEASE to userspace.
So far as fuse_release() waits for end of async I/O, its callbacks (fuse_readpages_end and fuse_writepage_finish) calling fuse_file_put cannot be the last holders of fuse file anymore. To emphasize the fact, the patch replaces fuse_file_put with __fuse_file_put there.
--- fs/fuse/file.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 52 insertions(+), 3 deletions(-)
fs/fuse/file.c +52 -3
@@ -137,6 +137,12 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) } } +static void __fuse_file_put (struct fuse_file *ff) +{ +if (atomic_dec_and_test (&ff->count)) +BUG (); +} +int fuse_do_open (struct fuse_conn *fc,u64 nodeid,struct file *file,bool isdir) {
@@ -260,7 +266,12 @@ void fuse_release_common(struct file *file, int opcode)* Make the release synchronous if this is a fuseblk mount, * synchronous RELEASE is allowed (and desirable) in this case * because the server can be trusted not to screw up. +* +* We might wait for them (asynchronous READ or WRITE requests), so: */ +if (ff->fc->close_wait) +BUG_ON (atomic_read (&ff->count) !=1 ); +fuse_file_put (ff, ff->fc->destroy_req !=NULL ); }
@@ -271,6 +282,31 @@ static int fuse_open(struct inode *inode, struct file *file)static int fuse_release (struct inode *inode,struct file *file) { +struct fuse_file *ff = file->private_data; + +if (ff->fc->close_wait) { +struct fuse_inode *fi =get_fuse_inode (inode); + +/* +* Must remove file from write list. Otherwise it is possible +* this file will get more writeback from another files +* rerouted via write_files. +*/ +spin_lock (&ff->fc->lock); +list_del_init (&ff->write_entry); +spin_unlock (&ff->fc->lock); + +wait_event (fi->page_waitq,atomic_read (&ff->count) ==1 ); + +/* +* Wait for threads just released ff to leave their critical +* sections. Taking spinlock is the first thing +* fuse_release_common does, so that this is unnecessary, but +* it is still good to emphasize right here, that we need this. +*/ +spin_unlock_wait (&ff->fc->lock); + } +fuse_release_common (file,FUSE_RELEASE );/* return value is ignored by VFS */
@@ -610,8 +646,17 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)unlock_page (page);page_cache_release (page); } -if (req->ff) -fuse_file_put (req->ff, false); +if (req->ff) { +if (fc->close_wait) { +struct fuse_inode *fi =get_fuse_inode (req->inode); + +spin_lock (&fc->lock); +__fuse_file_put (req->ff); +wake_up (&fi->page_waitq); +spin_unlock (&fc->lock); + }else +fuse_file_put (req->ff, false); + } }struct fuse_fill_data {
@@ -637,6 +682,7 @@ static void fuse_send_readpages(struct fuse_fill_data *data)if (fc->async_read) { req->ff =fuse_file_get (ff); req->end = fuse_readpages_end; + req->inode = data->inode;fuse_request_send_background (fc, req); }else {fuse_request_send (fc, req);
@@ -1178,7 +1224,8 @@ static ssize_t fuse_direct_write(struct file *file, const char __user *buf,static void fuse_writepage_free (struct fuse_conn *fc,struct fuse_req *req) {__free_page (req->pages[0 ]); -fuse_file_put (req->ff, false); +if (!fc->close_wait) +fuse_file_put (req->ff, false); }static void fuse_writepage_finish (struct fuse_conn *fc,struct fuse_req *req)
@@ -1191,6 +1238,8 @@ static void fuse_writepage_finish(struct fuse_conn *fc, struct fuse_req *req)dec_bdi_stat (bdi,BDI_WRITEBACK );dec_zone_page_state (req->pages[0 ],NR_WRITEBACK_TEMP );bdi_writeout_inc (bdi); +if (fc->close_wait) +__fuse_file_put (req->ff);wake_up (&fi->page_waitq); }
Re: [PATCH 3/5] fuse: wait for end of IO on release
Quoted 20 lines · Maxim Patlasov
There are two types of I/O activity that can be "in progress" at the time of
fuse_release()execution: asynchronous read-ahead and write-back. The patch ensures that they are completed beforefuse_release_commonsendsFUSE_RELEASEto userspace.So far as
fuse_release()waits for end of async I/O, its callbacks (fuse_readpages_endandfuse_writepage_finish) callingfuse_file_putcannot be the last holders of fuse file anymore. To emphasize the fact, the patch replacesfuse_file_putwith__fuse_file_putthere.Signed-off-by Maxim Patlasov <[email protected]>--- fs/fuse/file.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 52 insertions(+), 3 deletions(-)fs/fuse/file.c +0
-0
fs/fuse/file.c static void fuse_file_put(struct fuse_file *ff, bool sync) · line 145
Quoted 9 lines
fs/fuse/file.c +6
-0@@ -137,6 +137,12 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) } } +static void __fuse_file_put (struct fuse_file *ff) +{ +if (atomic_dec_and_test (&ff->count)) +BUG (); +} +
I think a comment in or before this function to explain the reasoning for the BUG would be helpful.
fs/fuse/file.c void fuse_release_common(struct file *file, int opcode) · line 274
Quoted 13 lines
fs/fuse/file.c +5
-0int fuse_do_open (struct fuse_conn *fc,u64 nodeid,struct file *file,bool isdir) {@@ -260,7 +266,12 @@ void fuse_release_common(struct file *file, int opcode)* Make the release synchronous if this is a fuseblk mount, * synchronous RELEASE is allowed (and desirable) in this case * because the server can be trusted not to screw up. +* +* We might wait for them (asynchronous READ or WRITE requests), so: */ +if (ff->fc->close_wait) +BUG_ON (atomic_read (&ff->count) !=1 ); +
It might be cleaner to pull the new part of the comment and the BUG_ON() check to before the existing comment and fuse_file_put (e.g., create a new comment).
Quoted 20 lines
fs/fuse/file.c +13
-0fuse_file_put (ff, ff->fc->destroy_req !=NULL ); }@@ -271,6 +282,31 @@ static int fuse_open(struct inode *inode, struct file *file)static int fuse_release (struct inode *inode,struct file *file) { +struct fuse_file *ff = file->private_data; + +if (ff->fc->close_wait) { +struct fuse_inode *fi =get_fuse_inode (inode); + +/* +* Must remove file from write list. Otherwise it is possible +* this file will get more writeback from another files +* rerouted via write_files. +*/ +spin_lock (&ff->fc->lock); +list_del_init (&ff->write_entry); +spin_unlock (&ff->fc->lock);
fs/fuse/file.c static int fuse_open(struct inode *inode, struct file *file) · line 307
Quoted 10 lines
fs/fuse/file.c +10
-0+ +wait_event (fi->page_waitq,atomic_read (&ff->count) ==1 ); + +/* +* Wait for threads just released ff to leave their critical +* sections. Taking spinlock is the first thing +* fuse_release_common does, so that this is unnecessary, but +* it is still good to emphasize right here, that we need this. +*/ +spin_unlock_wait (&ff->fc->lock);
I'm all for clarity, but if the wait is unnecessary, perhaps just leave the comment..? Just my .02.
Aside from the few nits here, the set looks pretty good to me.
Brian
Quoted 53 lines
fs/fuse/file.c +18
-3+ } +fuse_release_common (file,FUSE_RELEASE );/* return value is ignored by VFS */ @@ -610,8 +646,17 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)unlock_page (page);page_cache_release (page); } -if (req->ff) -fuse_file_put (req->ff, false); +if (req->ff) { +if (fc->close_wait) { +struct fuse_inode *fi =get_fuse_inode (req->inode); + +spin_lock (&fc->lock); +__fuse_file_put (req->ff); +wake_up (&fi->page_waitq); +spin_unlock (&fc->lock); + }else +fuse_file_put (req->ff, false); + } }struct fuse_fill_data {@@ -637,6 +682,7 @@ static void fuse_send_readpages(struct fuse_fill_data *data)if (fc->async_read) { req->ff =fuse_file_get (ff); req->end = fuse_readpages_end; + req->inode = data->inode;fuse_request_send_background (fc, req); }else {fuse_request_send (fc, req);@@ -1178,7 +1224,8 @@ static ssize_t fuse_direct_write(struct file *file, const char __user *buf,static void fuse_writepage_free (struct fuse_conn *fc,struct fuse_req *req) {__free_page (req->pages[0 ]); -fuse_file_put (req->ff, false); +if (!fc->close_wait) +fuse_file_put (req->ff, false); }static void fuse_writepage_finish (struct fuse_conn *fc,struct fuse_req *req)@@ -1191,6 +1238,8 @@ static void fuse_writepage_finish(struct fuse_conn *fc, struct fuse_req *req)dec_bdi_stat (bdi,BDI_WRITEBACK );dec_zone_page_state (req->pages[0 ],NR_WRITEBACK_TEMP );bdi_writeout_inc (bdi); +if (fc->close_wait) +__fuse_file_put (req->ff);wake_up (&fi->page_waitq); }
Re: [PATCH 3/5] fuse: wait for end of IO on release
Hi Brian,
01/03/2013 12:35 AM, Brian Foster пишет:
Quoted 83 lines · Maxim Patlasov
Quoted 20 lines
There are two types of I/O activity that can be "in progress" at the time of
fuse_release()execution: asynchronous read-ahead and write-back. The patch ensures that they are completed beforefuse_release_commonsendsFUSE_RELEASEto userspace.So far as
fuse_release()waits for end of async I/O, its callbacks (fuse_readpages_endandfuse_writepage_finish) callingfuse_file_putcannot be the last holders of fuse file anymore. To emphasize the fact, the patch replacesfuse_file_putwith__fuse_file_putthere.Signed-off-by Maxim Patlasov <[email protected]>--- fs/fuse/file.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 52 insertions(+), 3 deletions(-)fs/fuse/file.c +0
-0fs/fuse/file.c static void fuse_file_put(struct fuse_file *ff, bool sync) · line 145
Quoted 9 lines
fs/fuse/file.c +6
-0@@ -137,6 +137,12 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) } } +static void __fuse_file_put (struct fuse_file *ff) +{ +if (atomic_dec_and_test (&ff->count)) +BUG (); +} +I think a comment in or before this function to explain the reasoning for the BUG would be helpful.
fs/fuse/file.c void fuse_release_common(struct file *file, int opcode) · line 274
Quoted 13 lines
fs/fuse/file.c +5
-0int fuse_do_open (struct fuse_conn *fc,u64 nodeid,struct file *file,bool isdir) {@@ -260,7 +266,12 @@ void fuse_release_common(struct file *file, int opcode)* Make the release synchronous if this is a fuseblk mount, * synchronous RELEASE is allowed (and desirable) in this case * because the server can be trusted not to screw up. +* +* We might wait for them (asynchronous READ or WRITE requests), so: */ +if (ff->fc->close_wait) +BUG_ON (atomic_read (&ff->count) !=1 ); +It might be cleaner to pull the new part of the comment and the
BUG_ON()check to before the existing comment andfuse_file_put(e.g., create a new comment).Quoted 20 lines
fs/fuse/file.c +13
-0fuse_file_put (ff, ff->fc->destroy_req !=NULL ); }@@ -271,6 +282,31 @@ static int fuse_open(struct inode *inode, struct file *file)static int fuse_release (struct inode *inode,struct file *file) { +struct fuse_file *ff = file->private_data; + +if (ff->fc->close_wait) { +struct fuse_inode *fi =get_fuse_inode (inode); + +/* +* Must remove file from write list. Otherwise it is possible +* this file will get more writeback from another files +* rerouted via write_files. +*/ +spin_lock (&ff->fc->lock); +list_del_init (&ff->write_entry); +spin_unlock (&ff->fc->lock);fs/fuse/file.c static int fuse_open(struct inode *inode, struct file *file) · line 307
Quoted 10 lines
fs/fuse/file.c +10
-0+ +wait_event (fi->page_waitq,atomic_read (&ff->count) ==1 ); + +/* +* Wait for threads just released ff to leave their critical +* sections. Taking spinlock is the first thing +* fuse_release_common does, so that this is unnecessary, but +* it is still good to emphasize right here, that we need this. +*/ +spin_unlock_wait (&ff->fc->lock);I'm all for clarity, but if the wait is unnecessary, perhaps just leave the comment..? Just my .02.
Quoted 1 line
Aside from the few nits here, the set looks pretty good to me.
Thanks for review, the suggestions look reasonable. I'll resend corrected patch soon.
Maxim
PATCH 1/1 fuse: wait for end of IO on release (v2)
In reply to Maxim PatlasovThere are two types of I/O activity that can be "in progress" at the time of fuse_release() execution: asynchronous read-ahead and write-back. The patch ensures that they are completed before fuse_release_common sends FUSE_RELEASE to userspace.
So far as fuse_release() waits for end of async I/O, its callbacks (fuse_readpages_end and fuse_writepage_finish) calling fuse_file_put cannot be the last holders of fuse file anymore. To emphasize the fact, the patch replaces fuse_file_put with __fuse_file_put there.
Changed in v2: Improve comments, comment spin_unlock_wait out. Thanks to Brian for suggestions.
--- fs/fuse/file.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 58 insertions(+), 3 deletions(-)
fs/fuse/file.c +58 -3
@@ -137,6 +137,17 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) } } +/* +* Asynchronous callbacks may use it instead of fuse_file_put() because +* we guarantee that they are never last holders of ff. Hitting BUG() below +* will make clear any violation of the guarantee. +*/ +static void __fuse_file_put (struct fuse_file *ff) +{ +if (atomic_dec_and_test (&ff->count)) +BUG (); +} +int fuse_do_open (struct fuse_conn *fc,u64 nodeid,struct file *file,bool isdir) {
@@ -253,6 +264,13 @@ void fuse_release_common(struct file *file, int opcode) req->misc.release.path = file->f_path;/* +* No more in-flight asynchronous READ or WRITE requests if +* fuse file release is synchronous +*/ +if (ff->fc->close_wait) +BUG_ON (atomic_read (&ff->count) !=1 ); + +/* * Normally this will send the RELEASE request, however if * some asynchronous READ or WRITE requests are outstanding, * the sending will be delayed.
@@ -271,6 +289,30 @@ static int fuse_open(struct inode *inode, struct file *file)static int fuse_release (struct inode *inode,struct file *file) { +struct fuse_file *ff = file->private_data; + +if (ff->fc->close_wait) { +struct fuse_inode *fi =get_fuse_inode (inode); + +/* +* Must remove file from write list. Otherwise it is possible +* this file will get more writeback from another files +* rerouted via write_files. +*/ +spin_lock (&ff->fc->lock); +list_del_init (&ff->write_entry); +spin_unlock (&ff->fc->lock); + +wait_event (fi->page_waitq,atomic_read (&ff->count) ==1 ); + +/* +* spin_unlock_wait(&ff->fc->lock) would be natural here to +* wait for threads just released ff to leave their critical +* sections. But taking spinlock is the first thing +* fuse_release_common does, so that this is unnecessary. +*/ + } +fuse_release_common (file,FUSE_RELEASE );/* return value is ignored by VFS */
@@ -610,8 +652,17 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)unlock_page (page);page_cache_release (page); } -if (req->ff) -fuse_file_put (req->ff, false); +if (req->ff) { +if (fc->close_wait) { +struct fuse_inode *fi =get_fuse_inode (req->inode); + +spin_lock (&fc->lock); +__fuse_file_put (req->ff); +wake_up (&fi->page_waitq); +spin_unlock (&fc->lock); + }else +fuse_file_put (req->ff, false); + } }struct fuse_fill_data {
@@ -637,6 +688,7 @@ static void fuse_send_readpages(struct fuse_fill_data *data)if (fc->async_read) { req->ff =fuse_file_get (ff); req->end = fuse_readpages_end; + req->inode = data->inode;fuse_request_send_background (fc, req); }else {fuse_request_send (fc, req);
@@ -1178,7 +1230,8 @@ static ssize_t fuse_direct_write(struct file *file, const char __user *buf,static void fuse_writepage_free (struct fuse_conn *fc,struct fuse_req *req) {__free_page (req->pages[0 ]); -fuse_file_put (req->ff, false); +if (!fc->close_wait) +fuse_file_put (req->ff, false); }static void fuse_writepage_finish (struct fuse_conn *fc,struct fuse_req *req)
@@ -1191,6 +1244,8 @@ static void fuse_writepage_finish(struct fuse_conn *fc, struct fuse_req *req)dec_bdi_stat (bdi,BDI_WRITEBACK );dec_zone_page_state (req->pages[0 ],NR_WRITEBACK_TEMP );bdi_writeout_inc (bdi); +if (fc->close_wait) +__fuse_file_put (req->ff);wake_up (&fi->page_waitq); }
PATCH 4/5 fuse: enable close_wait feature
In reply to Maxim PatlasovThe patch enables feature by passing 'true' to fuse_file_put in fuse_release_common.
Previously, this was safe only in special cases when we sure that multi-threaded userspace won't deadlock if we'll synchronously send FUSE_RELEASE in the context of read-ahead or write-back callback. Now, it's always safe because callbacks don't send requests to userspace anymore.
--- fs/fuse/file.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
fs/fuse/file.c +2 -1
@@ -272,7 +272,8 @@ void fuse_release_common(struct file *file, int opcode)if (ff->fc->close_wait)BUG_ON (atomic_read (&ff->count) !=1 ); -fuse_file_put (ff, ff->fc->destroy_req !=NULL ); +fuse_file_put (ff, ff->fc->destroy_req !=NULL || + ff->fc->close_wait); }static int fuse_open (struct inode *inode,struct file *file)
PATCH 1/1 fuse: enable close_wait feature (v2)
The patch enables feature by passing 'true' to fuse_file_put in fuse_release_common.
Previously, this was safe only in special cases when we sure that multi-threaded userspace won't deadlock if we'll synchronously send FUSE_RELEASE in the context of read-ahead or write-back callback. Now, it's always safe because callbacks don't send requests to userspace anymore.
Changed in v2: update the patch to be applied cleanly on the top of previous modified (v2) patch of the patch-set.
--- fs/fuse/file.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
fs/fuse/file.c +2 -1
@@ -279,7 +279,8 @@ void fuse_release_common(struct file *file, int opcode)* synchronous RELEASE is allowed (and desirable) in this case * because the server can be trusted not to screw up. */ -fuse_file_put (ff, ff->fc->destroy_req !=NULL ); +fuse_file_put (ff, ff->fc->destroy_req !=NULL || + ff->fc->close_wait); }static int fuse_open (struct inode *inode,struct file *file)
PATCH 5/5 fuse: fix synchronous case of fuse_file_put()
In reply to Maxim PatlasovIf fuse_file_put() is called with sync==true, the user may be blocked for a while, until userspace ACKs our FUSE_RELEASE request. This blocking must be uninterruptible. Otherwise request could be interrupted, but file association in user space remains.
--- fs/fuse/file.c | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-)
fs/fuse/file.c +4 -0
@@ -126,6 +126,10 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)struct fuse_req *req = ff->reserved_req;if (sync) { +/* Must force. Otherwise request could be interrupted, +* but file association in user space remains. +*/ + req->force =1 ;fuse_request_send (ff->fc, req);path_put (&req->misc.release.path);fuse_put_request (ff->fc, req);
Hi Miklos,
Any feedback would be highly appreciated.
Thanks,
Maxim
12/20/2012 04:30 PM, Maxim Patlasov пишет:
Quoted 47 lines
Hi,
There is a long-standing demand for syncronous behaviour of
fuse_release:http://sourceforge.net/mailarchive/message.php?msg_id=19343889
http://sourceforge.net/mailarchive/message.php?msg_id=29814693A few months ago Avati and me explained why such a feature would be useful:
http://sourceforge.net/mailarchive/message.php?msg_id=29889055
http://sourceforge.net/mailarchive/message.php?msg_id=29867423In short, the problem is that
fuse_release(that's called on last user close(2)) sendsFUSE_RELEASEto userspace and returns without waiting for ACK from userspace. Consequently, there is a gap when user regards the file released while userspace fuse is still working on it. An attempt to access the file from another node leads to complicated synchronization problems because the first node still "holds" the file.The patch-set resolves the problem by making
fuse_releasesynchronous:
wait for ACK from userspace forFUSE_RELEASEif the feature is ON.To keep single-threaded userspace implementations happy the patch-set ensures that by the time
fuse_release_commoncallsfuse_file_put, no more in-flight I/O exists. Asynchronous fuse callbacks (likefuse_readpages_end) cannot triggerFUSE_RELEASEanymore. Hence, we'll never block in contexts other thanclose().Thanks,
Maxim---
Maxim Patlasov (5):
fuse: addclose_waitflag tofuse_conn
fuse: cosmetic rework offuse_send_readpages
fuse: wait for end of IO on release
fuse: enableclose_waitfeature
fuse: fix synchronous case offuse_file_put()fs/fuse/file.c | 82 ++++++++++++++++++++++++++++++++++++++------- fs/fuse/fuse_i.h | 3 ++ fs/fuse/inode.c | 5 ++- include/uapi/linux/fuse.h | 7 +++- 4 files changed, 82 insertions(+), 15 deletions(-)
Miklos Szeredi
maintainerQuoted 19 lines · Maxim Patlasov
Hi,
There is a long-standing demand for syncronous behaviour of
fuse_release:http://sourceforge.net/mailarchive/message.php?msg_id=19343889
http://sourceforge.net/mailarchive/message.php?msg_id=29814693A few months ago Avati and me explained why such a feature would be useful:
http://sourceforge.net/mailarchive/message.php?msg_id=29889055
http://sourceforge.net/mailarchive/message.php?msg_id=29867423In short, the problem is that
fuse_release(that's called on last user close(2)) sendsFUSE_RELEASEto userspace and returns without waiting for ACK from userspace. Consequently, there is a gap when user regards the file released while userspace fuse is still working on it. An attempt to access the file from another node leads to complicated synchronization problems because the first node still "holds" the file.
Quoted 8 lines
The patch-set resolves the problem by making
fuse_releasesynchronous:
wait for ACK from userspace forFUSE_RELEASEif the feature is ON.To keep single-threaded userspace implementations happy the patch-set ensures that by the time
fuse_release_commoncallsfuse_file_put, no more in-flight I/O exists. Asynchronous fuse callbacks (likefuse_readpages_end) cannot triggerFUSE_RELEASEanymore. Hence, we'll never block in contexts other thanclose().
There are a few fput() calls outside sys_close(), all of these can trigger FUSE_RELEASE. Most of those are OK, but for some I'm reluctant to enable synchronous release.
For example doing a readlink() on a magic symlink under /proc shouldn't result in a synchronous call to a fuse filesystem. Making fput() synchronous may actually end up doing that (even if it's not very likely).
At least for the unprivileged fuse daemon case it shouldn't be done. If the fuse daemon can be "trusted" then enabling synchronous release should be okay, that's why it's enabled for fuseblk.
But maybe I'm just too paranoid...
Thanks,
Miklos
Miklos Szeredi
maintainerQuoted 4 lines · Miklos Szeredi
For example doing a
readlink()on a magic symlink under /proc shouldn't result in a synchronous call to a fuse filesystem. Makingfput()synchronous may actually end up doing that (even if it's not very likely).
Thinking about this a bit more. As it is it sounds wrong to rely on a synchronous release, when in fact release is just not synchronous, as indicated by the above example. Maybe it's the proc code that's buggy and shouldn't do get_file/fput because everyone is assuming release being synchronous with close(). Don't know.
Let's approach it from the other direction: what if you give back the write lease on the first flush? It will probably work fine for 99% of cases, since no other writes are going to happen after the first flush. For the remaining cases you'll just have to reacquire the lease when a write happens after the flush. I guess performance-wise that will not be an issue, but I may be wrong.
Thanks,
Miklos
Quoted 11 lines · Miklos Szeredi
Quoted 4 lines · Miklos Szeredi
For example doing a
readlink()on a magic symlink under /proc shouldn't result in a synchronous call to a fuse filesystem. Makingfput()synchronous may actually end up doing that (even if it's not very likely).Thinking about this a bit more. As it is it sounds wrong to rely on a synchronous release, when in fact release is just not synchronous, as indicated by the above example. Maybe it's the proc code that's buggy and shouldn't do get_file/fput because everyone is assuming release being synchronous with
close(). Don't know.
What the hell? ->release() is not and has never been synchronous with close(). There is any number of places where the final fput() might be called and no, this readlink example is irrelevant - things like munmap()/dup2()/close of a socket discarding a datagram with the last reference to struct file in it, et sodding cetera.
Hell, another thread might be in the middle of read(2) at the moment when you call close(). Result: the final fput() will be done when we are about to return from that read(2).
People, ->release() is *NOT* guaranteed to be anywhere near close(2). Never had been.
Miklos Szeredi
maintainerQuoted 19 lines · Al Viro
Quoted 11 lines · Miklos Szeredi
Quoted 4 lines · Miklos Szeredi
For example doing a
readlink()on a magic symlink under /proc shouldn't result in a synchronous call to a fuse filesystem. Makingfput()synchronous may actually end up doing that (even if it's not very likely).Thinking about this a bit more. As it is it sounds wrong to rely on a synchronous release, when in fact release is just not synchronous, as indicated by the above example. Maybe it's the proc code that's buggy and shouldn't do get_file/fput because everyone is assuming release being synchronous with
close(). Don't know.What the hell? ->release() is not and has never been synchronous with
close(). There is any number of places where the finalfput()might be called and no, this readlink example is irrelevant - things like munmap()/dup2()/close of a socket discarding a datagram with the last reference to struct file in it, et sodding cetera.
Quoted 3 lines
Hell, another thread might be in the middle of read(2) at the moment when you call
close(). Result: the finalfput()will be done when we are about to return from that read(2).
Apparently we do make some pains to make ->release() return before the syscall that triggered it returns. Why is that then?
I think the difference between proc symlink and all the rest is that everything the app does to the file descriptor is its own business. If it just does single threaded open, read/write, close (which is what the vast majority of apps do) then close *is* going to be synchronous with release. At least most of the time.
Thanks,
Miklos
Miklos Szeredi
maintainerQuoted 6 lines · Miklos Szeredi
Let's approach it from the other direction: what if you give back the write lease on the first flush? It will probably work fine for 99% of cases, since no other writes are going to happen after the first flush. For the remaining cases you'll just have to reacquire the lease when a write happens after the flush. I guess performance-wise that will not be an issue, but I may be wrong.
What about this?
Thanks,
Miklos
Quoted 8 lines · Miklos Szeredi
Quoted 6 lines · Miklos Szeredi
Let's approach it from the other direction: what if you give back the write lease on the first flush? It will probably work fine for 99% of cases, since no other writes are going to happen after the first flush. For the remaining cases you'll just have to reacquire the lease when a write happens after the flush. I guess performance-wise that will not be an issue, but I may be wrong.
What about this?
We'd like to do it, but we can't. Firstly because we rely on the fact that the file cannot be modified by someone else while we hold exclusive write lease. By the time we decide to reacquire the lease, the file can be re-used by someone else and become completely different comparatively with its state at the moment of first flush. Secondly, we can't sensibly handle a case when the lease is already acquired by someone else by the time of attempt to reacquire it.
Thanks,
Maxim
Hi Miklos,
Quoted 37 lines · Miklos Szeredi
Quoted 19 lines · Maxim Patlasov
Hi,
There is a long-standing demand for syncronous behaviour of
fuse_release:http://sourceforge.net/mailarchive/message.php?msg_id=19343889
http://sourceforge.net/mailarchive/message.php?msg_id=29814693A few months ago Avati and me explained why such a feature would be useful:
http://sourceforge.net/mailarchive/message.php?msg_id=29889055
http://sourceforge.net/mailarchive/message.php?msg_id=29867423In short, the problem is that
fuse_release(that's called on last user close(2)) sendsFUSE_RELEASEto userspace and returns without waiting for ACK from userspace. Consequently, there is a gap when user regards the file released while userspace fuse is still working on it. An attempt to access the file from another node leads to complicated synchronization problems because the first node still "holds" the file.Quoted 8 lines
The patch-set resolves the problem by making
fuse_releasesynchronous:
wait for ACK from userspace forFUSE_RELEASEif the feature is ON.To keep single-threaded userspace implementations happy the patch-set ensures that by the time
fuse_release_commoncallsfuse_file_put, no more in-flight I/O exists. Asynchronous fuse callbacks (likefuse_readpages_end) cannot triggerFUSE_RELEASEanymore. Hence, we'll never block in contexts other thanclose().There are a few
fput()calls outsidesys_close(), all of these can triggerFUSE_RELEASE. Most of those are OK, but for some I'm reluctant to enable synchronous release.For example doing a
readlink()on a magic symlink under /proc shouldn't result in a synchronous call to a fuse filesystem. Makingfput()synchronous may actually end up doing that (even if it's not very likely).
Quoted 5 lines
At least for the unprivileged fuse daemon case it shouldn't be done. If the fuse daemon can be "trusted" then enabling synchronous release should be okay, that's why it's enabled for fuseblk.
But maybe I'm just too paranoid...
No, I don't think it's too paranoid. I suggest to put the feature under fusermount control by adding "close_wait" mount option. This is very simple and straightforward and let sysad to decide whether to allow the feature for unprivileged users or not.
Btw, having read last messages on this thread, I realized that the name of patchset is a bit misleading - it would be better to name it "process last fput() synchronously". But the core idea still looks sensible to me: userspace may hold a reference to a file in one way or another (e.g. by mmap-ed region), but when all references are released the file should be ready for reuse again (e.g. to be accessed from another node).
The patch-set was reviewed by Brian Foster and now you looked at it as well. Is it time for me to rebase the patchset to be applied on top of writeback-cache patches?
Thanks,
Maxim