Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 88703267 authored by KAMEZAWA Hiroyuki's avatar KAMEZAWA Hiroyuki Committed by Linus Torvalds
Browse files

cgroup avoid permanent sleep at rmdir



After commit ec64f515 ("cgroup: fix
frequent -EBUSY at rmdir"), cgroup's rmdir (especially against memcg)
doesn't return -EBUSY by temporary ref counts.  That commit expects all
refs after pre_destroy() is temporary but...it wasn't.  Then, rmdir can
wait permanently.  This patch tries to fix that and change followings.

 - set CGRP_WAIT_ON_RMDIR flag before pre_destroy().
 - clear CGRP_WAIT_ON_RMDIR flag when the subsys finds racy case.
   if there are sleeping ones, wakes them up.
 - rmdir() sleeps only when CGRP_WAIT_ON_RMDIR flag is set.

Tested-by: default avatarDaisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Reported-by: default avatarDaisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Reviewed-by: default avatarPaul Menage <menage@google.com>
Acked-by: default avatarBalbir Sigh <balbir@linux.vnet.ibm.com>
Signed-off-by: default avatarKAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent f0d83679
Loading
Loading
Loading
Loading
+17 −0
Original line number Original line Diff line number Diff line
@@ -362,6 +362,23 @@ int cgroup_task_count(const struct cgroup *cgrp);
/* Return true if cgrp is a descendant of the task's cgroup */
/* Return true if cgrp is a descendant of the task's cgroup */
int cgroup_is_descendant(const struct cgroup *cgrp, struct task_struct *task);
int cgroup_is_descendant(const struct cgroup *cgrp, struct task_struct *task);


/*
 * When the subsys has to access css and may add permanent refcnt to css,
 * it should take care of racy conditions with rmdir(). Following set of
 * functions, is for stop/restart rmdir if necessary.
 * Because these will call css_get/put, "css" should be alive css.
 *
 *  cgroup_exclude_rmdir();
 *  ...do some jobs which may access arbitrary empty cgroup
 *  cgroup_release_and_wakeup_rmdir();
 *
 *  When someone removes a cgroup while cgroup_exclude_rmdir() holds it,
 *  it sleeps and cgroup_release_and_wakeup_rmdir() will wake him up.
 */

void cgroup_exclude_rmdir(struct cgroup_subsys_state *css);
void cgroup_release_and_wakeup_rmdir(struct cgroup_subsys_state *css);

/*
/*
 * Control Group subsystem type.
 * Control Group subsystem type.
 * See Documentation/cgroups/cgroups.txt for details
 * See Documentation/cgroups/cgroups.txt for details
+38 −17
Original line number Original line Diff line number Diff line
@@ -735,16 +735,28 @@ static void cgroup_d_remove_dir(struct dentry *dentry)
 * reference to css->refcnt. In general, this refcnt is expected to goes down
 * reference to css->refcnt. In general, this refcnt is expected to goes down
 * to zero, soon.
 * to zero, soon.
 *
 *
 * CGRP_WAIT_ON_RMDIR flag is modified under cgroup's inode->i_mutex;
 * CGRP_WAIT_ON_RMDIR flag is set under cgroup's inode->i_mutex;
 */
 */
DECLARE_WAIT_QUEUE_HEAD(cgroup_rmdir_waitq);
DECLARE_WAIT_QUEUE_HEAD(cgroup_rmdir_waitq);


static void cgroup_wakeup_rmdir_waiters(const struct cgroup *cgrp)
static void cgroup_wakeup_rmdir_waiter(struct cgroup *cgrp)
{
{
	if (unlikely(test_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags)))
	if (unlikely(test_and_clear_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags)))
		wake_up_all(&cgroup_rmdir_waitq);
		wake_up_all(&cgroup_rmdir_waitq);
}
}


void cgroup_exclude_rmdir(struct cgroup_subsys_state *css)
{
	css_get(css);
}

void cgroup_release_and_wakeup_rmdir(struct cgroup_subsys_state *css)
{
	cgroup_wakeup_rmdir_waiter(css->cgroup);
	css_put(css);
}


static int rebind_subsystems(struct cgroupfs_root *root,
static int rebind_subsystems(struct cgroupfs_root *root,
			      unsigned long final_bits)
			      unsigned long final_bits)
{
{
@@ -1359,7 +1371,7 @@ int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
	 * wake up rmdir() waiter. the rmdir should fail since the cgroup
	 * wake up rmdir() waiter. the rmdir should fail since the cgroup
	 * is no longer empty.
	 * is no longer empty.
	 */
	 */
	cgroup_wakeup_rmdir_waiters(cgrp);
	cgroup_wakeup_rmdir_waiter(cgrp);
	return 0;
	return 0;
}
}


@@ -2743,33 +2755,42 @@ static int cgroup_rmdir(struct inode *unused_dir, struct dentry *dentry)
	}
	}
	mutex_unlock(&cgroup_mutex);
	mutex_unlock(&cgroup_mutex);


	/*
	 * In general, subsystem has no css->refcnt after pre_destroy(). But
	 * in racy cases, subsystem may have to get css->refcnt after
	 * pre_destroy() and it makes rmdir return with -EBUSY. This sometimes
	 * make rmdir return -EBUSY too often. To avoid that, we use waitqueue
	 * for cgroup's rmdir. CGRP_WAIT_ON_RMDIR is for synchronizing rmdir
	 * and subsystem's reference count handling. Please see css_get/put
	 * and css_tryget() and cgroup_wakeup_rmdir_waiter() implementation.
	 */
	set_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags);

	/*
	/*
	 * Call pre_destroy handlers of subsys. Notify subsystems
	 * Call pre_destroy handlers of subsys. Notify subsystems
	 * that rmdir() request comes.
	 * that rmdir() request comes.
	 */
	 */
	ret = cgroup_call_pre_destroy(cgrp);
	ret = cgroup_call_pre_destroy(cgrp);
	if (ret)
	if (ret) {
		clear_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags);
		return ret;
		return ret;
	}


	mutex_lock(&cgroup_mutex);
	mutex_lock(&cgroup_mutex);
	parent = cgrp->parent;
	parent = cgrp->parent;
	if (atomic_read(&cgrp->count) || !list_empty(&cgrp->children)) {
	if (atomic_read(&cgrp->count) || !list_empty(&cgrp->children)) {
		clear_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags);
		mutex_unlock(&cgroup_mutex);
		mutex_unlock(&cgroup_mutex);
		return -EBUSY;
		return -EBUSY;
	}
	}
	/*
	 * css_put/get is provided for subsys to grab refcnt to css. In typical
	 * case, subsystem has no reference after pre_destroy(). But, under
	 * hierarchy management, some *temporal* refcnt can be hold.
	 * To avoid returning -EBUSY to a user, waitqueue is used. If subsys
	 * is really busy, it should return -EBUSY at pre_destroy(). wake_up
	 * is called when css_put() is called and refcnt goes down to 0.
	 */
	set_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags);
	prepare_to_wait(&cgroup_rmdir_waitq, &wait, TASK_INTERRUPTIBLE);
	prepare_to_wait(&cgroup_rmdir_waitq, &wait, TASK_INTERRUPTIBLE);

	if (!cgroup_clear_css_refs(cgrp)) {
	if (!cgroup_clear_css_refs(cgrp)) {
		mutex_unlock(&cgroup_mutex);
		mutex_unlock(&cgroup_mutex);
		/*
		 * Because someone may call cgroup_wakeup_rmdir_waiter() before
		 * prepare_to_wait(), we need to check this flag.
		 */
		if (test_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags))
			schedule();
			schedule();
		finish_wait(&cgroup_rmdir_waitq, &wait);
		finish_wait(&cgroup_rmdir_waitq, &wait);
		clear_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags);
		clear_bit(CGRP_WAIT_ON_RMDIR, &cgrp->flags);
@@ -3342,7 +3363,7 @@ void __css_put(struct cgroup_subsys_state *css)
			set_bit(CGRP_RELEASABLE, &cgrp->flags);
			set_bit(CGRP_RELEASABLE, &cgrp->flags);
			check_for_release(cgrp);
			check_for_release(cgrp);
		}
		}
		cgroup_wakeup_rmdir_waiters(cgrp);
		cgroup_wakeup_rmdir_waiter(cgrp);
	}
	}
	rcu_read_unlock();
	rcu_read_unlock();
}
}
+20 −3
Original line number Original line Diff line number Diff line
@@ -1207,6 +1207,12 @@ static int mem_cgroup_move_account(struct page_cgroup *pc,
	ret = 0;
	ret = 0;
out:
out:
	unlock_page_cgroup(pc);
	unlock_page_cgroup(pc);
	/*
	 * We charges against "to" which may not have any tasks. Then, "to"
	 * can be under rmdir(). But in current implementation, caller of
	 * this function is just force_empty() and it's garanteed that
	 * "to" is never removed. So, we don't check rmdir status here.
	 */
	return ret;
	return ret;
}
}


@@ -1428,6 +1434,7 @@ __mem_cgroup_commit_charge_swapin(struct page *page, struct mem_cgroup *ptr,
		return;
		return;
	if (!ptr)
	if (!ptr)
		return;
		return;
	cgroup_exclude_rmdir(&ptr->css);
	pc = lookup_page_cgroup(page);
	pc = lookup_page_cgroup(page);
	mem_cgroup_lru_del_before_commit_swapcache(page);
	mem_cgroup_lru_del_before_commit_swapcache(page);
	__mem_cgroup_commit_charge(ptr, pc, ctype);
	__mem_cgroup_commit_charge(ptr, pc, ctype);
@@ -1457,8 +1464,12 @@ __mem_cgroup_commit_charge_swapin(struct page *page, struct mem_cgroup *ptr,
		}
		}
		rcu_read_unlock();
		rcu_read_unlock();
	}
	}
	/* add this page(page_cgroup) to the LRU we want. */
	/*

	 * At swapin, we may charge account against cgroup which has no tasks.
	 * So, rmdir()->pre_destroy() can be called while we do this charge.
	 * In that case, we need to call pre_destroy() again. check it here.
	 */
	cgroup_release_and_wakeup_rmdir(&ptr->css);
}
}


void mem_cgroup_commit_charge_swapin(struct page *page, struct mem_cgroup *ptr)
void mem_cgroup_commit_charge_swapin(struct page *page, struct mem_cgroup *ptr)
@@ -1664,7 +1675,7 @@ void mem_cgroup_end_migration(struct mem_cgroup *mem,


	if (!mem)
	if (!mem)
		return;
		return;

	cgroup_exclude_rmdir(&mem->css);
	/* at migration success, oldpage->mapping is NULL. */
	/* at migration success, oldpage->mapping is NULL. */
	if (oldpage->mapping) {
	if (oldpage->mapping) {
		target = oldpage;
		target = oldpage;
@@ -1704,6 +1715,12 @@ void mem_cgroup_end_migration(struct mem_cgroup *mem,
	 */
	 */
	if (ctype == MEM_CGROUP_CHARGE_TYPE_MAPPED)
	if (ctype == MEM_CGROUP_CHARGE_TYPE_MAPPED)
		mem_cgroup_uncharge_page(target);
		mem_cgroup_uncharge_page(target);
	/*
	 * At migration, we may charge account against cgroup which has no tasks
	 * So, rmdir()->pre_destroy() can be called while we do this charge.
	 * In that case, we need to call pre_destroy() again. check it here.
	 */
	cgroup_release_and_wakeup_rmdir(&mem->css);
}
}


/*
/*