cgroup: fix broken css_has_online_children()
Subsystems (3)
PATCH 1/5 cgroup: fix broken css_has_online_children()
After running:
# mount -t cgroup cpu xxx /cgroup && mkdir /cgroup/sub && \
rmdir /cgroup/sub && umount /cgroupI found the cgroup root still existed:
# cat /proc/cgroups #subsys_name hierarchy num_cgroups enabled cpuset 0 1 1 cpu 1 1 1 ...
It turned out css_has_online_children() is broken.
--- kernel/cgroup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/cgroup.c +1 -1
@@ -3327,7 +3327,7 @@ bool css_has_online_children(struct cgroup_subsys_state *css)rcu_read_lock ();css_for_each_child (child, css) { -if (css->flags &CSS_ONLINE ) { +if (child->flags &CSS_ONLINE ) { ret = true;break ; }
Signature · 1 line
1.8.0.2
PATCH 2/5 percpu-ref: introduce percpu_ref_alive()
This is used to check if the percpu_ref has been killed.
--- include/linux/percpu-refcount.h | 11 +++++++++++ 1 file changed, 11 insertions(+)
include/linux/percpu-refcount.h +11 -0
@@ -96,6 +96,17 @@ static inline void percpu_ref_kill(struct percpu_ref *ref)#define REF_STATUS (count) (((unsigned long) count) & PCPU_STATUS_MASK)/** +* percpu_ref_alive - check if the ref has been killed +* @ref: percpu_ref to check +* +* Return true if percpu_ref_kill() has been called to drop the initial ref. +*/ +static inline bool percpu_ref_alive (struct percpu_ref *ref) +{ +return !(REF_STATUS (ref->pcpu_count) ==PCPU_REF_DEAD ); +} + +/** * percpu_ref_get - increment a percpu refcount * @ref: percpu_ref to get *
Signature · 1 line
1.8.0.2
PATCH 3/5 cgroup: fix mount failure in a corner case
In reply to Li Zefan# cat test.sh #! /bin/bash
mount -t cgroup -o cpu xxx /cgroup
umount /cgroup
mount -t cgroup -o cpu,cpuacct xxx /cgroup
umount /cgroup
# ./test.sh mount: xxx already mounted or /cgroup busy mount: according to mtab, xxx is already mounted on /cgroup
It's because the cgroupfs_root of the first mount was under destruction asynchronously.
Fix this by delaying and then retrying mount in this case.
--- kernel/cgroup.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
kernel/cgroup.c +18 -0
@@ -1648,10 +1648,12 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type, int flags, const char *unused_dev_name, void *data) { + struct cgroup_subsys *ss;struct cgroup_root *root;struct cgroup_sb_opts opts;struct dentry *dentry;int ret; +int i;bool new_sb;/*
@@ -1677,6 +1679,22 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type, goto out_unlock; } +/* +* If some subsystems have been bound to existing cgroup hierarchies, +* but those hierachies are being destroyed, let's wait a little bit +* and retry. +*/ +for_each_subsys (ss, i) { +if (!(opts.subsys_mask & (1 << i))) +continue ; +if (!percpu_ref_alive (&ss->root->cgrp.self.refcnt)) { +mutex_unlock (&cgroup_mutex); +msleep (10 ); + ret =restart_syscall (); + goto out_free; + } + } +for_each_root (root) {bool name_match = false;
Signature · 1 line
1.8.0.2
Tejun Heo
maintainerRe: [PATCH 3/5] cgroup: fix mount failure in a corner case
kernel/cgroup.c static struct dentry *cgroup_mount(struct file_system_type *fs_type, · line 1690
Quoted 13 lines · Li Zefan
kernel/cgroup.c +9
-0@@ -1677,6 +1679,22 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type, goto out_unlock; } +/* +* If some subsystems have been bound to existing cgroup hierarchies, +* but those hierachies are being destroyed, let's wait a little bit +* and retry. +*/ +for_each_subsys (ss, i) { +if (!(opts.subsys_mask & (1 << i))) +continue ; +if (!percpu_ref_alive (&ss->root->cgrp.self.refcnt)) {
Can't we just do tryget_live() instead and then put before retrying? It's not exactly a hot path and the operations are dirt cheap anyway.
Thanks.
Signature · 1 line
tejun
Re: [PATCH 3/5] cgroup: fix mount failure in a corner case
Quoted 5 lines · Tejun Heo
Quoted 4 lines · Li Zefan
kernel/cgroup.c +0
-0@@ -1677,6 +1679,22 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type, goto out_unlock; }
Quoted 13 lines
kernel/cgroup.c static struct dentry *cgroup_mount(struct file_system_type *fs_type, · line 1690
Quoted 9 lines
kernel/cgroup.c +9
-0+/* +* If some subsystems have been bound to existing cgroup hierarchies, +* but those hierachies are being destroyed, let's wait a little bit +* and retry. +*/ +for_each_subsys (ss, i) { +if (!(opts.subsys_mask & (1 << i))) +continue ; +if (!percpu_ref_alive (&ss->root->cgrp.self.refcnt)) {Can't we just do
tryget_live()instead and then put before retrying? It's not exactly a hot path and the operations are dirt cheap anyway.
No much difference, though would be a bit more code. I can do that.
PATCH 4/5 kernfs: introduce kernfs_pin_sb() and kernfs_drop_sb()
In reply to Li Zefankernfs_pin_sb() tries to get a refcnt of the superblock, while kernfs_drop_sb() drops this refcnt.
This will be used by cgroupfs.
--- fs/kernfs/mount.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/kernfs.h | 3 +++ 2 files changed, 48 insertions(+)
fs/kernfs/mount.c +45 -0
@@ -210,6 +210,51 @@ void kernfs_kill_sb(struct super_block *sb)kernfs_put (root_kn); } +/** +* kernfs_pin_sb: try to pin the superblock associated with a kernfs_root +* @kernfs_root: the kernfs_root in question +* @ns: the namespace tag +* +* Pin the superblock so the superblock won't be destroyed in subsequent +* operations. +*/ +bool kernfs_pin_sb (struct kernfs_root *root,const void *ns) +{ +struct kernfs_super_info *info; +int ret = false; + +mutex_lock (&kernfs_mutex); +list_for_each_entry (info, &root->supers, node) { +if (info->ns == ns) { + ret =atomic_inc_not_zero (&info->sb->s_active); +break ; + } + } +mutex_unlock (&kernfs_mutex); +return ret; +} + +/** +* kernfs_drop_sb: drop the refcnt that we got by kernfs_pin_sb() +* @root: the kernfs_root in question +* @ns: the namespace tag +* +* This must be paired with kernfs_pin_sb(). It will require sb->u_mount +* if the refcnt reaches zero. +*/ +void kernfs_drop_sb (struct kernfs_root *root,const void *ns) +{ +struct kernfs_super_info *info; + +mutex_lock (&kernfs_mutex); +list_for_each_entry (info, &root->supers, node) { +if (info->ns == ns) +break ; + } +mutex_unlock (&kernfs_mutex); +deactivate_super (info->sb); +} +void __init kernfs_init (void ) { kernfs_node_cache = kmem_cache_create("kernfs_node_cache",
include/linux/kernfs.h +3 -0
@@ -288,6 +288,9 @@ struct dentry *kernfs_mount_ns(struct file_system_type *fs_type, int flags, const void *ns);void kernfs_kill_sb (struct super_block *sb); +bool kernfs_pin_sb (struct kernfs_root *root,const void *ns); +void kernfs_drop_sb (struct kernfs_root *root,const void *ns); +void kernfs_init (void );#else /* CONFIG_KERNFS */
Signature · 1 line
1.8.0.2
PATCH 5/5 cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
In reply to Li ZefanWe've converted cgroup to kernfs so cgroup won't be intertwined with vfs objects and locking, but there are dark areas.
Run two instances of this script concurrently:
for ((; ;))
{
mount -t cgroup -o cpuacct xxx /cgroup
umount /cgroup
}After a while, I saw two mount processes were stuck at retrying, because they were waiting for a subsystem to become free, but the root associated with this subsystem never got freed.
This can happen, if thread A is in the process of killing superblock but hasn't called percpu_ref_kill(), and at this time thread B is mounting the same cgroup root and finds the root in the root list and performs percpu_ref_try_get().
To fix this, we increase the refcnt of the superblock instead of increasing the percpu refcnt of cgroup root.
---
A better fix is welcome!
--- kernel/cgroup.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-)
kernel/cgroup.c +14 -10
@@ -1654,7 +1654,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,struct dentry *dentry;int ret;int i; -bool new_sb; +bool sb_pinned = false;/* * The first time anyone tries to mount a cgroup, enable the list
@@ -1735,19 +1735,21 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type, }/* -* A root's lifetime is governed by its root cgroup. -* tryget_live failure indicate that the root is being -* destroyed. Wait for destruction to complete so that the -* subsystems are free. We can use wait_queue for the wait -* but this path is super cold. Let's just sleep for a bit -* and retry. +* This may fail for two reasons: +* - A concurrent mount is in process. We wait for that mount +to complete. +* - The superblock is being destroyed. We wait for the +* desctruction to complete so that the subsystems are free. +* We can use wait_queue for the wait but this path is super +* cold. Let's just sleep for a bit and retry. */ -if (!percpu_ref_tryget_live (&root->cgrp.self.refcnt)) { +if (!kernfs_pin_sb (root->kf_root,NULL )) {mutex_unlock (&cgroup_mutex);msleep (10 ); ret =restart_syscall (); goto out_free; } + sb_pinned = true; ret =0 ; goto out_unlock;
@@ -1784,8 +1786,10 @@ out_free:if (ret)return ERR_PTR (ret); - dentry =kernfs_mount (fs_type, flags, root->kf_root, &new_sb); -if (IS_ERR (dentry) || !new_sb) + dentry =kernfs_mount (fs_type, flags, root->kf_root,NULL ); +if (sb_pinned) +kernfs_drop_sb (root->kf_root,NULL ); +if (!sb_pinned &&IS_ERR (dentry))cgroup_put (&root->cgrp);return dentry; }
Signature · 1 line
1.8.0.2
Tejun Heo
maintainerRe: [PATCH 5/5] cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
Hello, Li.
Sorry about the long delay.
Quoted 15 lines · Li Zefan
We've converted cgroup to kernfs so cgroup won't be intertwined with vfs objects and locking, but there are dark areas.
Run two instances of this script concurrently:
for ((; ;)) { mount -t cgroup -o cpuacct xxx /cgroup umount /cgroup }After a while, I saw two mount processes were stuck at retrying, because they were waiting for a subsystem to become free, but the root associated with this subsystem never got freed.
Quoted 7 lines
This can happen, if thread A is in the process of killing superblock but hasn't called
percpu_ref_kill(), and at this time thread B is mounting the same cgroup root and finds the root in the root list and performspercpu_ref_try_get().To fix this, we increase the refcnt of the superblock instead of increasing the percpu refcnt of cgroup root.
Ah, right. Gees, I'm really hating the fact that we have ->mount but not ->umount. However, can't we make it a bit simpler by just introducing a mutex protecting looking up and refing up an existing root and a sb going away? The only problem is that the refcnt being killed isn't atomic w.r.t. new live ref coming up, right? Why not just add a mutex around them so that they can't race?
Thanks.
Signature · 1 line
tejun
Re: [PATCH 5/5] cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
Quoted 28 lines · Tejun Heo
Hello, Li.
Sorry about the long delay.
Quoted 15 lines · Li Zefan
We've converted cgroup to kernfs so cgroup won't be intertwined with vfs objects and locking, but there are dark areas.
Run two instances of this script concurrently:
for ((; ;)) { mount -t cgroup -o cpuacct xxx /cgroup umount /cgroup }After a while, I saw two mount processes were stuck at retrying, because they were waiting for a subsystem to become free, but the root associated with this subsystem never got freed.
Quoted 7 lines
This can happen, if thread A is in the process of killing superblock but hasn't called
percpu_ref_kill(), and at this time thread B is mounting the same cgroup root and finds the root in the root list and performspercpu_ref_try_get().To fix this, we increase the refcnt of the superblock instead of increasing the percpu refcnt of cgroup root.
Quoted 7 lines
Ah, right. Gees, I'm really hating the fact that we have ->mount but not ->umount. However, can't we make it a bit simpler by just introducing a mutex protecting looking up and refing up an existing root and a sb going away? The only problem is that the refcnt being killed isn't atomic w.r.t. new live ref coming up, right? Why not just add a mutex around them so that they can't race?
Well, kill_sb() is called with sb->s_umount held, while kernfs_mount() returned with sb->s_umount held, so adding a mutex will lead to ABBA deadlock.
Tejun Heo
maintainerRe: [PATCH 5/5] cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
Hello, Li.
Quoted 10 lines · Li Zefan
Quoted 6 lines
Ah, right. Gees, I'm really hating the fact that we have ->mount but not ->umount. However, can't we make it a bit simpler by just introducing a mutex protecting looking up and refing up an existing root and a sb going away? The only problem is that the refcnt being killed isn't atomic w.r.t. new live ref coming up, right? Why not just add a mutex around them so that they can't race?
Well,
kill_sb()is called withsb->s_umountheld, whilekernfs_mount()returned withsb->s_umountheld, so adding a mutex will lead to ABBA deadlock.
Hmmm? Why does that matter? The only region in cgroup_mount() which needs to be put inside such mutex would be root lookup, no?
Thanks.
Signature · 1 line
tejun
Re: [PATCH 5/5] cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
Quoted 10 lines · Tejun Heo
Hello, Li.
Quoted 7 lines · Li Zefan
Quoted 6 lines
Ah, right. Gees, I'm really hating the fact that we have ->mount but not ->umount. However, can't we make it a bit simpler by just introducing a mutex protecting looking up and refing up an existing root and a sb going away? The only problem is that the refcnt being killed isn't atomic w.r.t. new live ref coming up, right? Why not just add a mutex around them so that they can't race?
Quoted 7 lines
Quoted 3 lines
Well,
kill_sb()is called withsb->s_umountheld, whilekernfs_mount()returned withsb->s_umountheld, so adding a mutex will lead to ABBA deadlock.Hmmm? Why does that matter? The only region in
cgroup_mount()which needs to be put inside such mutex would be root lookup, no?
unfortunately that won't help. I think what you suggest is:
cgroup_mount()
{
mutex_lock();
lookup_cgroup_root();
mutex_unlock();
kernfs_mount();
}cgroup_kill_sb()
{
mutex_lock();
percpu_ref_kill();
mutex_Unlock();
kernfs_kill_sb();
}See, we may still be destroying the superblock after we've succeeded in getting the refcnt of cgroup root.
Tejun Heo
maintainerRe: [PATCH 5/5] cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
Hey,
Quoted 21 lines · Li Zefan
Quoted 2 lines
Hmmm? Why does that matter? The only region in
cgroup_mount()which needs to be put inside such mutex would be root lookup, no?unfortunately that won't help. I think what you suggest is:
cgroup_mount(){ mutex_lock(); lookup_cgroup_root(); mutex_unlock(); kernfs_mount(); }
cgroup_kill_sb(){ mutex_lock(); percpu_ref_kill(); mutex_Unlock(); kernfs_kill_sb(); }
Quoted 2 lines
See, we may still be destroying the superblock after we've succeeded in getting the refcnt of cgroup root.
Sure, but now the decision to kill is synchronized so the other side can interlock with it. e.g.
cgroup_mount()
{
mutex_lock();
lookup_cgroup_root();
if (root isn't killed yet)
root->this_better_stay_alive++;
mutex_unlock();
kernfs_mount();
}cgroup_kill_sb()
{
mutex_lock();
if (check whether root can be killed)
percpu_ref_kill();
mutex_unlock();
if (the above condition was true)
kernfs_kill_sb();
}Signature · 1 line
tejun
Re: [PATCH 5/5] cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
Quoted 41 lines · Tejun Heo
Hey,
Quoted 21 lines · Li Zefan
Quoted 2 lines
Hmmm? Why does that matter? The only region in
cgroup_mount()which needs to be put inside such mutex would be root lookup, no?unfortunately that won't help. I think what you suggest is:
cgroup_mount(){ mutex_lock(); lookup_cgroup_root(); mutex_unlock(); kernfs_mount(); }
cgroup_kill_sb(){ mutex_lock(); percpu_ref_kill(); mutex_Unlock(); kernfs_kill_sb(); }Quoted 2 lines
See, we may still be destroying the superblock after we've succeeded in getting the refcnt of cgroup root.
Sure, but now the decision to kill is synchronized so the other side can interlock with it. e.g.
cgroup_mount(){ mutex_lock(); lookup_cgroup_root(); if (root isn't killed yet) root->this_better_stay_alive++; mutex_unlock(); kernfs_mount(); }
cgroup_kill_sb()
Quoted 9 lines
{
mutex_lock();
if (check whether root can be killed)
percpu_ref_kill();
mutex_unlock();
if (the above condition was true)
kernfs_kill_sb();
}This looks nasty, and I don't think it's correct. If we skip the call to kernfs_kill_sb(), kernfs_super_info won't be freed but super_block will, so we will end up either leaking memory or accessing invalid memory. There are other problems like returning with sb->s_umount still held.
Tejun Heo
maintainerRe: [PATCH 5/5] cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()
Quoted 21 lines · Li Zefan
Quoted 11 lines
cgroup_mount(){ mutex_lock(); lookup_cgroup_root(); if (root isn't killed yet) root->this_better_stay_alive++; mutex_unlock(); kernfs_mount(); }
cgroup_kill_sb()Quoted 9 lines
{ mutex_lock(); if (check whether root can be killed) percpu_ref_kill(); mutex_unlock(); if (the above condition was true) kernfs_kill_sb(); }
Quoted 5 lines
This looks nasty, and I don't think it's correct. If we skip the call to
kernfs_kill_sb(),kernfs_super_infowon't be freed butsuper_blockwill, so we will end up either leaking memory or accessing invalid memory. There are other problems like returning withsb->s_umountstill held.
Yeah, right, the conditional shouldn't be on kernfs_kill_sb(). It should only be on percpu_ref_kill(). kernfs mount code will wait out the dead sb and create a new one; however, this is still not feasible because we don't have a place to dec ->this_better_stay_alive as there's no umount callback. :(
Thanks.
Signature · 1 line
tejun
Quoted 13 lines · Li Zefan
After running:
# mount -t cgroup cpu xxx /cgroup && mkdir /cgroup/sub && \ rmdir /cgroup/sub && umount /cgroupI found the cgroup root still existed:
# cat /proc/cgroups #subsys_name hierarchy num_cgroups enabled cpuset 0 1 1 cpu 1 1 1 ...
Quoted 3 lines
It turned out
css_has_online_children()is broken.Signed-off-by Li Zefan <[email protected]>
Applied to cgroup/for-3.16-fixes.
Thanks.
Signature · 1 line
tejun