writeback: keep superblock pinned during cgroup writeback association switches
If cgroup writeback is in use, an inode is associated with a cgroup
for writeback. If the inode's main dirtier changes to another cgroup,
the association gets updated asynchronously. Nothing was pinning the
superblock while such switches are in progress and superblock could go
away while async switching is pending or in progress leading to
crashes like the following.
kernel BUG at fs/jbd2/transaction.c:319!
invalid opcode: 0000 [#1] SMP DEBUG_PAGEALLOC
CPU: 1 PID: 29158 Comm: kworker/1:10 Not tainted 4.5.0-rc3 #51
Hardware name: Google Google, BIOS Google 01/01/2011
Workqueue: events inode_switch_wbs_work_fn
task: ffff880213dbbd40 ti: ffff880209264000 task.ti: ffff880209264000
RIP: 0010:[<ffffffff803e6922>] [<ffffffff803e6922>] start_this_handle+0x382/0x3e0
RSP: 0018:ffff880209267c30 EFLAGS: 00010202
...
Call Trace:
[<ffffffff803e6be4>] jbd2__journal_start+0xf4/0x190
[<ffffffff803cfc7e>] __ext4_journal_start_sb+0x4e/0x70
[<ffffffff803b31ec>] ext4_evict_inode+0x12c/0x3d0
[<ffffffff8035338b>] evict+0xbb/0x190
[<ffffffff80354190>] iput+0x130/0x190
[<ffffffff80360223>] inode_switch_wbs_work_fn+0x343/0x4c0
[<ffffffff80279819>] process_one_work+0x129/0x300
[<ffffffff80279b16>] worker_thread+0x126/0x480
[<ffffffff8027ed14>] kthread+0xc4/0xe0
[<ffffffff809771df>] ret_from_fork+0x3f/0x70
Fix it by bumping s_active while cgroup association switching is in
flight.
Signed-off-by: Tejun Heo <tj@kernel.org>
Reported-and-tested-by: Tahsin Erdogan <tahsin@google.com>
Link: http://lkml.kernel.org/g/CAAeU0aNCq7LGODvVGRU-oU_o-6enii5ey0p1c26D1ZzYwkDc5A@mail.gmail.com
Fixes: d10c809552
("writeback: implement foreign cgroup inode bdi_writeback switching")
Cc: stable@vger.kernel.org #v4.5+
Signed-off-by: Jens Axboe <axboe@fb.com>
This commit is contained in:
Родитель
2d99b55d37
Коммит
5ff8eaac16
|
@ -317,6 +317,7 @@ static void inode_switch_wbs_work_fn(struct work_struct *work)
|
||||||
struct inode_switch_wbs_context *isw =
|
struct inode_switch_wbs_context *isw =
|
||||||
container_of(work, struct inode_switch_wbs_context, work);
|
container_of(work, struct inode_switch_wbs_context, work);
|
||||||
struct inode *inode = isw->inode;
|
struct inode *inode = isw->inode;
|
||||||
|
struct super_block *sb = inode->i_sb;
|
||||||
struct address_space *mapping = inode->i_mapping;
|
struct address_space *mapping = inode->i_mapping;
|
||||||
struct bdi_writeback *old_wb = inode->i_wb;
|
struct bdi_writeback *old_wb = inode->i_wb;
|
||||||
struct bdi_writeback *new_wb = isw->new_wb;
|
struct bdi_writeback *new_wb = isw->new_wb;
|
||||||
|
@ -423,6 +424,7 @@ skip_switch:
|
||||||
wb_put(new_wb);
|
wb_put(new_wb);
|
||||||
|
|
||||||
iput(inode);
|
iput(inode);
|
||||||
|
deactivate_super(sb);
|
||||||
kfree(isw);
|
kfree(isw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -469,11 +471,14 @@ static void inode_switch_wbs(struct inode *inode, int new_wb_id)
|
||||||
|
|
||||||
/* while holding I_WB_SWITCH, no one else can update the association */
|
/* while holding I_WB_SWITCH, no one else can update the association */
|
||||||
spin_lock(&inode->i_lock);
|
spin_lock(&inode->i_lock);
|
||||||
|
|
||||||
if (inode->i_state & (I_WB_SWITCH | I_FREEING) ||
|
if (inode->i_state & (I_WB_SWITCH | I_FREEING) ||
|
||||||
inode_to_wb(inode) == isw->new_wb) {
|
inode_to_wb(inode) == isw->new_wb)
|
||||||
spin_unlock(&inode->i_lock);
|
goto out_unlock;
|
||||||
goto out_free;
|
|
||||||
}
|
if (!atomic_inc_not_zero(&inode->i_sb->s_active))
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
inode->i_state |= I_WB_SWITCH;
|
inode->i_state |= I_WB_SWITCH;
|
||||||
spin_unlock(&inode->i_lock);
|
spin_unlock(&inode->i_lock);
|
||||||
|
|
||||||
|
@ -489,6 +494,8 @@ static void inode_switch_wbs(struct inode *inode, int new_wb_id)
|
||||||
call_rcu(&isw->rcu_head, inode_switch_wbs_rcu_fn);
|
call_rcu(&isw->rcu_head, inode_switch_wbs_rcu_fn);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
out_unlock:
|
||||||
|
spin_unlock(&inode->i_lock);
|
||||||
out_free:
|
out_free:
|
||||||
if (isw->new_wb)
|
if (isw->new_wb)
|
||||||
wb_put(isw->new_wb);
|
wb_put(isw->new_wb);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче