Skip to contentSkip to conversation

cgroup: fix broken css_has_online_children()

PATCH Applied Li Zefan 2 participants 15 messages Jun 12, 2014 – Jun 27, 2014

Subsystems (3)
lore
Share & tools
lore

Li Zefan

author
#0 lore raw

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 /cgroup

I 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
index 05b8ca4..1c65f24 100644
@@ -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
author
#1 lore raw

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
index dba35c4..1d5f2b3 100644
@@ -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
author
#2 lore raw

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
index 1c65f24..bd37e8d 100644
@@ -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
maintainer
#3 lore raw

Re: [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
author
#4 lore raw

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.

author
#5 lore raw

PATCH 4/5 kernfs: introduce kernfs_pin_sb() and kernfs_drop_sb()

In reply to Li Zefan

kernfs_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
index f25a7c0..4f924e0 100644
@@ -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
index 589318b..1958017 100644
@@ -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
author
#6 lore raw

PATCH 5/5 cgroup: fix a race between cgroup_mount() and cgroup_kill_sb()

In reply to 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.

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
index bd37e8d..94e1814 100644
@@ -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
maintainer
#7 lore raw

Re: [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 performs percpu_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
author
#8 lore raw

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 performs percpu_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.

maintainer
#9 lore raw

Re: [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 with sb->s_umount held, while kernfs_mount() returned with sb->s_umount held, 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
author
#10 lore raw

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 with sb->s_umount held, while kernfs_mount() returned with sb->s_umount held, 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.

maintainer
#11 lore raw

Re: [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
author
#12 lore raw

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.

maintainer
#13 lore raw

Re: [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_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.

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
maintainer Applied
#14 lore raw
In reply to Li Zefan
Quoted 13 lines · Li Zefan

After running:

  # mount -t cgroup cpu xxx /cgroup && mkdir /cgroup/sub && \
    rmdir /cgroup/sub && umount /cgroup

I 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.

Applied to cgroup/for-3.16-fixes.

Thanks.

Signature · 1 line
tejun