mirror of
https://github.com/adulau/aha.git
synced 2024-12-28 03:36:19 +00:00
cgroup avoid permanent sleep at rmdir
After commit ec64f51545
("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: Daisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Reported-by: Daisuke Nishimura <nishimura@mxp.nes.nec.co.jp>
Reviewed-by: Paul Menage <menage@google.com>
Acked-by: Balbir Sigh <balbir@linux.vnet.ibm.com>
Signed-off-by: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
f0d83679a8
commit
887032670d
3 changed files with 75 additions and 20 deletions
|
@ -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
|
||||||
|
|
|
@ -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,34 +2755,43 @@ again:
|
||||||
}
|
}
|
||||||
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);
|
||||||
schedule();
|
/*
|
||||||
|
* 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();
|
||||||
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);
|
||||||
if (signal_pending(current))
|
if (signal_pending(current))
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in a new issue