locks: defer freeing locks in locks_delete_lock until after i_lock has been dropped
In commit 72f98e7255
(locks: turn lock_flocks into a spinlock), we
moved from using the BKL to a global spinlock. With this change, we lost
the ability to block in the fl_release_private operation.
This is problematic for NFS (and probably some other filesystems as
well). Add a new list_head argument to locks_delete_lock. If that
argument is non-NULL, then queue any locks that we want to free to the
list instead of freeing them.
Then, add a new locks_dispose_list function that will walk such a list
and call locks_free_lock on them after the i_lock has been dropped.
Finally, change all of the callers of locks_delete_lock to pass in a
list_head, except for lease_modify. That function can be called long
after the i_lock has been acquired. Deferring the freeing of a lease
after unlocking it in that function is non-trivial until we overhaul
some of the spinlocking in the lease code.
Currently though, no filesystem that sets fl_release_private supports
leases, so this is not currently a problem. We'll eventually want to
make the same change in the lease code, but it needs a lot more work
before we can reasonably do so.
Acked-by: J. Bruce Fields <bfields@fieldses.org>
Signed-off-by: Jeff Layton <jlayton@primarydata.com>
This commit is contained in:
parent
b84d49f944
commit
ed9814d858
38
fs/locks.c
38
fs/locks.c
|
@ -247,6 +247,18 @@ void locks_free_lock(struct file_lock *fl)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(locks_free_lock);
|
EXPORT_SYMBOL(locks_free_lock);
|
||||||
|
|
||||||
|
static void
|
||||||
|
locks_dispose_list(struct list_head *dispose)
|
||||||
|
{
|
||||||
|
struct file_lock *fl;
|
||||||
|
|
||||||
|
while (!list_empty(dispose)) {
|
||||||
|
fl = list_first_entry(dispose, struct file_lock, fl_block);
|
||||||
|
list_del_init(&fl->fl_block);
|
||||||
|
locks_free_lock(fl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void locks_init_lock(struct file_lock *fl)
|
void locks_init_lock(struct file_lock *fl)
|
||||||
{
|
{
|
||||||
memset(fl, 0, sizeof(struct file_lock));
|
memset(fl, 0, sizeof(struct file_lock));
|
||||||
|
@ -651,12 +663,16 @@ static void locks_unlink_lock(struct file_lock **thisfl_p)
|
||||||
*
|
*
|
||||||
* Must be called with i_lock held!
|
* Must be called with i_lock held!
|
||||||
*/
|
*/
|
||||||
static void locks_delete_lock(struct file_lock **thisfl_p)
|
static void locks_delete_lock(struct file_lock **thisfl_p,
|
||||||
|
struct list_head *dispose)
|
||||||
{
|
{
|
||||||
struct file_lock *fl = *thisfl_p;
|
struct file_lock *fl = *thisfl_p;
|
||||||
|
|
||||||
locks_unlink_lock(thisfl_p);
|
locks_unlink_lock(thisfl_p);
|
||||||
locks_free_lock(fl);
|
if (dispose)
|
||||||
|
list_add(&fl->fl_block, dispose);
|
||||||
|
else
|
||||||
|
locks_free_lock(fl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Determine if lock sys_fl blocks lock caller_fl. Common functionality
|
/* Determine if lock sys_fl blocks lock caller_fl. Common functionality
|
||||||
|
@ -812,6 +828,7 @@ static int flock_lock_file(struct file *filp, struct file_lock *request)
|
||||||
struct inode * inode = file_inode(filp);
|
struct inode * inode = file_inode(filp);
|
||||||
int error = 0;
|
int error = 0;
|
||||||
int found = 0;
|
int found = 0;
|
||||||
|
LIST_HEAD(dispose);
|
||||||
|
|
||||||
if (!(request->fl_flags & FL_ACCESS) && (request->fl_type != F_UNLCK)) {
|
if (!(request->fl_flags & FL_ACCESS) && (request->fl_type != F_UNLCK)) {
|
||||||
new_fl = locks_alloc_lock();
|
new_fl = locks_alloc_lock();
|
||||||
|
@ -834,7 +851,7 @@ static int flock_lock_file(struct file *filp, struct file_lock *request)
|
||||||
if (request->fl_type == fl->fl_type)
|
if (request->fl_type == fl->fl_type)
|
||||||
goto out;
|
goto out;
|
||||||
found = 1;
|
found = 1;
|
||||||
locks_delete_lock(before);
|
locks_delete_lock(before, &dispose);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,6 +898,7 @@ static int flock_lock_file(struct file *filp, struct file_lock *request)
|
||||||
spin_unlock(&inode->i_lock);
|
spin_unlock(&inode->i_lock);
|
||||||
if (new_fl)
|
if (new_fl)
|
||||||
locks_free_lock(new_fl);
|
locks_free_lock(new_fl);
|
||||||
|
locks_dispose_list(&dispose);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,6 +912,7 @@ static int __posix_lock_file(struct inode *inode, struct file_lock *request, str
|
||||||
struct file_lock **before;
|
struct file_lock **before;
|
||||||
int error;
|
int error;
|
||||||
bool added = false;
|
bool added = false;
|
||||||
|
LIST_HEAD(dispose);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We may need two file_lock structures for this operation,
|
* We may need two file_lock structures for this operation,
|
||||||
|
@ -989,7 +1008,7 @@ static int __posix_lock_file(struct inode *inode, struct file_lock *request, str
|
||||||
else
|
else
|
||||||
request->fl_end = fl->fl_end;
|
request->fl_end = fl->fl_end;
|
||||||
if (added) {
|
if (added) {
|
||||||
locks_delete_lock(before);
|
locks_delete_lock(before, &dispose);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
request = fl;
|
request = fl;
|
||||||
|
@ -1019,7 +1038,7 @@ static int __posix_lock_file(struct inode *inode, struct file_lock *request, str
|
||||||
* one (This may happen several times).
|
* one (This may happen several times).
|
||||||
*/
|
*/
|
||||||
if (added) {
|
if (added) {
|
||||||
locks_delete_lock(before);
|
locks_delete_lock(before, &dispose);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -1035,7 +1054,7 @@ static int __posix_lock_file(struct inode *inode, struct file_lock *request, str
|
||||||
locks_copy_lock(new_fl, request);
|
locks_copy_lock(new_fl, request);
|
||||||
request = new_fl;
|
request = new_fl;
|
||||||
new_fl = NULL;
|
new_fl = NULL;
|
||||||
locks_delete_lock(before);
|
locks_delete_lock(before, &dispose);
|
||||||
locks_insert_lock(before, request);
|
locks_insert_lock(before, request);
|
||||||
added = true;
|
added = true;
|
||||||
}
|
}
|
||||||
|
@ -1097,6 +1116,7 @@ static int __posix_lock_file(struct inode *inode, struct file_lock *request, str
|
||||||
locks_free_lock(new_fl);
|
locks_free_lock(new_fl);
|
||||||
if (new_fl2)
|
if (new_fl2)
|
||||||
locks_free_lock(new_fl2);
|
locks_free_lock(new_fl2);
|
||||||
|
locks_dispose_list(&dispose);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1272,7 +1292,7 @@ int lease_modify(struct file_lock **before, int arg)
|
||||||
printk(KERN_ERR "locks_delete_lock: fasync == %p\n", fl->fl_fasync);
|
printk(KERN_ERR "locks_delete_lock: fasync == %p\n", fl->fl_fasync);
|
||||||
fl->fl_fasync = NULL;
|
fl->fl_fasync = NULL;
|
||||||
}
|
}
|
||||||
locks_delete_lock(before);
|
locks_delete_lock(before, NULL);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2324,6 +2344,7 @@ void locks_remove_file(struct file *filp)
|
||||||
struct inode * inode = file_inode(filp);
|
struct inode * inode = file_inode(filp);
|
||||||
struct file_lock *fl;
|
struct file_lock *fl;
|
||||||
struct file_lock **before;
|
struct file_lock **before;
|
||||||
|
LIST_HEAD(dispose);
|
||||||
|
|
||||||
if (!inode->i_flock)
|
if (!inode->i_flock)
|
||||||
return;
|
return;
|
||||||
|
@ -2369,12 +2390,13 @@ void locks_remove_file(struct file *filp)
|
||||||
fl->fl_type, fl->fl_flags,
|
fl->fl_type, fl->fl_flags,
|
||||||
fl->fl_start, fl->fl_end);
|
fl->fl_start, fl->fl_end);
|
||||||
|
|
||||||
locks_delete_lock(before);
|
locks_delete_lock(before, &dispose);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
before = &fl->fl_next;
|
before = &fl->fl_next;
|
||||||
}
|
}
|
||||||
spin_unlock(&inode->i_lock);
|
spin_unlock(&inode->i_lock);
|
||||||
|
locks_dispose_list(&dispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue