Let the commit task hold transactions

The commit work sets trans_holds so that all hold attempts block while
it's doing its work.  Now that it's calling in to generic vfs functions
to write out dirty file data it can end up in generic write functions
that try to hold the trans and can deadlock.

This adds tracking of the commit task so that holds know to let it
proceed without deadlocking.

Signed-off-by: Zach Brown <zab@versity.com>
Reviewed-by: Mark Fasheh <mfasheh@versity.com>
This commit is contained in:
Zach Brown
2016-11-09 13:02:12 -08:00
parent 256166db32
commit 1d0cd95b55
2 changed files with 15 additions and 0 deletions

View File

@@ -34,6 +34,7 @@ struct scoutfs_sb_info {
atomic_t trans_holds;
wait_queue_head_t trans_hold_wq;
struct task_struct *trans_task;
spinlock_t trans_write_lock;
u64 trans_write_count;

View File

@@ -51,6 +51,10 @@
* holding a transaction so it doesn't have to worry about blocks being
* dirtied while it is working.
*
* In the course of doing its work this task might need to use write
* functions that would try to hold the transaction. We record the task
* whose committing the transaction so that holding won't deadlock.
*
* Any dirty block had to have allocated a new blkno which would have
* created dirty allocator metadata blocks. We can avoid writing
* entirely if we don't have any dirty metadata blocks. This is
@@ -74,6 +78,8 @@ void scoutfs_trans_write_func(struct work_struct *work)
int ret = 0;
bool have_umount;
sbi->trans_task = current;
wait_event(sbi->trans_hold_wq,
atomic_cmpxchg(&sbi->trans_holds, 0, -1) == 0);
@@ -111,6 +117,8 @@ void scoutfs_trans_write_func(struct work_struct *work)
atomic_set(&sbi->trans_holds, 0);
wake_up(&sbi->trans_hold_wq);
sbi->trans_task = NULL;
}
struct write_attempt {
@@ -173,6 +181,9 @@ int scoutfs_hold_trans(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
if (current == sbi->trans_task)
return 0;
return wait_event_interruptible(sbi->trans_hold_wq,
atomic_add_unless(&sbi->trans_holds, 1, -1));
}
@@ -188,6 +199,9 @@ void scoutfs_release_trans(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
if (current == sbi->trans_task)
return;
if (atomic_sub_return(1, &sbi->trans_holds) == 0) {
if (scoutfs_buddy_alloc_count(sb) >= SCOUTFS_MAX_TRANS_BLOCKS)
scoutfs_sync_fs(sb, 0);