Skip to contentSkip to conversation

freezer for cgroup v2

PATCH Superseded Roman Gushchin 4 participants 21 messages Feb 19, 2019 – Mar 5, 2019

Subsystems (4)

v8 of 10 Superseded Latest is v10 →

lore
Share & tools
lore
#0 lore raw

v8 cover freezer for cgroup v2

This patchset implements freezer for cgroup v2.

It provides similar functionality as v1 freezer, but the interface conforms to the cgroup v2 interface design principles, and it provides a better user experience: tasks can be killed, ptrace works, there is no separate controller, which has to be enabled, etc.

Patches (1), (2) and (3) are some preparational work, patch (4) contains the implementation, patch (5) is a small cgroup kselftest fix, patch (6) covers freezer adds 6 new kselftests to cover the freezer functionality. Patch (7) adds corresponding docs.

v8->v7:
- reworked/simplified cgroup frozen task accounting by merging nr_stopped and nr_frozen and removing nr_tasks_to_freeze
- don't notify the parent process if the child is going from the stopped to the frozen state

v7->v6:
- handle properly the case, when a task is both stopped and frozen
- check for CGRP_FREEZE instead of CGRP_FROZEN in cgroup_exit()
- minor cosmetic changes and rebase

v6->v5:
- reverted to clear TIF_SIGPENDING with additional checks before schedule(), as proposed by Oleg Nesterov
- made cgroup v2 freezer working with the system freezer (by using freezable_schedule())
- make freezer working with SIGSTOPped and PTRACEd tasks
- added tests to cover freezing a cgroup with SIGSTOPped and PTRACEd tasks

v5->v4:
- rewored cgroup state transition code (suggested by Tejun Heo)
- look at JOBCTL_TRAP_FREEZE instead of task->frozen in recalc_sigpending(), check for task->frozen and JOBCTL_TRAP_FREEZE in signal_pending_state() (suggested by Oleg Nesterov)
- some cosmetic changes in signal.c (suggested by Oleg Nesterov)
- cleaned up comments

v4->v3:
- reading nr_descendants doesn't require taking css_set_lock anymore
- fixed docs based on Mike Rapoport's feedback
- fixed double irq lock found by Dan Carpenter

v3->v2:
- dropped TASK_FROZEN for now, frozen tasks are put into TASK_INTERRUPTIBLE state; it's probably not the final version, but the API question can be discussed separately
- don't clear TIF_SIGPENDING before going to sleep, instead add task->frozen check in signal_pending_state() and recalc_sigpending()
- cgroup-level counter are now synchronized using css_set_lock, which simplified the whole code (e.g. per-cgroup works were removed)
- the amount of comments increased significantly
- many other improvements incorporating feedback from Tejun and Oleg

v2->v1:
- fixed locking aroung calling cgroup_freezer_leave()
- added docs

Roman Gushchin (7):
cgroup: rename freezer.c into legacy_freezer.c
cgroup: implement __cgroup_task_count() helper
cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock
cgroup: cgroup v2 freezer
kselftests: cgroup: don't fail on cg_kill_all() error in cg_destroy()
kselftests: cgroup: add freezer controller self-tests
cgroup: document cgroup v2 freezer interface

 Documentation/admin-guide/cgroup-v2.rst       |  27 +
 include/linux/cgroup-defs.h                   |  33 +
 include/linux/cgroup.h                        |  42 +
 include/linux/sched.h                         |   2 +
 include/linux/sched/jobctl.h                  |   2 +
 kernel/cgroup/Makefile                        |   4 +-
 kernel/cgroup/cgroup-internal.h               |   1 +
 kernel/cgroup/cgroup-v1.c                     |  16 -
 kernel/cgroup/cgroup.c                        | 142 ++-
 kernel/cgroup/freezer.c                       | 608 +++++--------
 kernel/cgroup/legacy_freezer.c                | 481 ++++++++++
 kernel/signal.c                               |  84 +-
 tools/testing/selftests/cgroup/.gitignore     |   1 +
 tools/testing/selftests/cgroup/Makefile       |   2 +
 tools/testing/selftests/cgroup/cgroup_util.c  |  85 +-
 tools/testing/selftests/cgroup/cgroup_util.h  |   7 +
 tools/testing/selftests/cgroup/test_freezer.c | 842 ++++++++++++++++++
 17 files changed, 1943 insertions(+), 436 deletions(-)
 create mode 100644 kernel/cgroup/legacy_freezer.c
 create mode 100644 tools/testing/selftests/cgroup/test_freezer.c
Signature · 1 line
2.20.1
#1 lore raw

PATCH v8 1/7 cgroup: rename freezer.c into legacy_freezer.c

Freezer.c will contain an implementation of cgroup v2 freezer, so let's rename the v1 freezer to avoid naming conflicts.

---
 kernel/cgroup/Makefile                        | 2 +-
 kernel/cgroup/{freezer.c => legacy_freezer.c} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename kernel/cgroup/{freezer.c => legacy_freezer.c} (100%)
kernel/cgroup/Makefile +1 -1
index bfcdae896122..8d5689ca94b9 100644
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o
 
-obj-$(CONFIG_CGROUP_FREEZER) += freezer.o
+obj-$(CONFIG_CGROUP_FREEZER) += legacy_freezer.o
 obj-$(CONFIG_CGROUP_PIDS) += pids.o
 obj-$(CONFIG_CGROUP_RDMA) += rdma.o
 obj-$(CONFIG_CPUSETS) += cpuset.o
kernel/cgroup/legacy_freezer.c renamed from kernel/cgroup/freezer.c +0 -0
similarity index 100%
rename from kernel/cgroup/freezer.c
rename to kernel/cgroup/legacy_freezer.c
Signature · 1 line
2.20.1
#2 lore raw

PATCH v8 2/7 cgroup: implement __cgroup_task_count() helper

In reply to Roman Gushchin

The helper is identical to the existing cgroup_task_count() except it doesn't take the css_set_lock by itself, assuming that the caller does.

Also, move cgroup_task_count() implementation into kernel/cgroup/cgroup.c, as there is nothing specific to cgroup v1.

---
 kernel/cgroup/cgroup-internal.h |  1 +
 kernel/cgroup/cgroup-v1.c       | 16 ----------------
 kernel/cgroup/cgroup.c          | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 34 insertions(+), 16 deletions(-)
kernel/cgroup/cgroup-internal.h +1 -0
index c950864016e2..a195328431ce 100644
@@ -226,6 +226,7 @@ int cgroup_rmdir(struct kernfs_node *kn);
 int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,
 		     struct kernfs_root *kf_root);
 
+int __cgroup_task_count(const struct cgroup *cgrp);
 int cgroup_task_count(const struct cgroup *cgrp);
 
 /*
kernel/cgroup/cgroup-v1.c +0 -16
index 583b969b0c0e..29c0ca8f76cf 100644
@@ -339,22 +339,6 @@ static struct cgroup_pidlist *cgroup_pidlist_find_create(struct cgroup *cgrp,
 	return l;
 }
 
-/**
- * cgroup_task_count - count the number of tasks in a cgroup.
- * @cgrp: the cgroup in question
- */
-int cgroup_task_count(const struct cgroup *cgrp)
-{
-	int count = 0;
-	struct cgrp_cset_link *link;
-
-	spin_lock_irq(&css_set_lock);
-	list_for_each_entry(link, &cgrp->cset_links, cset_link)
-		count += link->cset->nr_tasks;
-	spin_unlock_irq(&css_set_lock);
-	return count;
-}
-
 /*
  * Load a cgroup's pidarray with either procs' tgids or tasks' pids
  */
kernel/cgroup/cgroup.c +33 -0
index f4418371c83b..b230afccf635 100644
@@ -592,6 +592,39 @@ static void cgroup_get_live(struct cgroup *cgrp)
 	css_get(&cgrp->self);
 }
 
+/**
+ * __cgroup_task_count - count the number of tasks in a cgroup. The caller
+ * is responsible for taking the css_set_lock.
+ * @cgrp: the cgroup in question
+ */
+int __cgroup_task_count(const struct cgroup *cgrp)
+{
+	int count = 0;
+	struct cgrp_cset_link *link;
+
+	lockdep_assert_held(&css_set_lock);
+
+	list_for_each_entry(link, &cgrp->cset_links, cset_link)
+		count += link->cset->nr_tasks;
+
+	return count;
+}
+
+/**
+ * cgroup_task_count - count the number of tasks in a cgroup.
+ * @cgrp: the cgroup in question
+ */
+int cgroup_task_count(const struct cgroup *cgrp)
+{
+	int count;
+
+	spin_lock_irq(&css_set_lock);
+	count = __cgroup_task_count(cgrp);
+	spin_unlock_irq(&css_set_lock);
+
+	return count;
+}
+
 struct cgroup_subsys_state *of_css(struct kernfs_open_file *of)
 {
 	struct cgroup *cgrp = of->kn->parent->priv;
Signature · 1 line
2.20.1
#3 lore raw

PATCH v8 3/7 cgroup: protect cgroup->nr_(dying_)descendants by css_set_lock

In reply to Roman Gushchin

The number of descendant cgroups and the number of dying descendant cgroups are currently synchronized using the cgroup_mutex.

The number of descendant cgroups will be required by the cgroup v2 freezer, which will use it to determine if a cgroup is frozen (depending on total number of descendants and number of frozen descendants). It's not always acceptable to grab the cgroup_mutex, especially from quite hot paths (e.g. exit()).

To avoid this, let's additionally synchronize these counters using the css_set_lock.

So, it's safe to read these counters with either cgroup_mutex or css_set_lock locked, and for changing both locks should be acquired.

---
 include/linux/cgroup-defs.h | 5 +++++
 kernel/cgroup/cgroup.c      | 6 ++++++
 2 files changed, 11 insertions(+)
include/linux/cgroup-defs.h +5 -0
index 120d1d40704b..319c07305500 100644
@@ -348,6 +348,11 @@ struct cgroup {
 	 * Dying cgroups are cgroups which were deleted by a user,
 	 * but are still existing because someone else is holding a reference.
 	 * max_descendants is a maximum allowed number of descent cgroups.
+	 *
+	 * nr_descendants and nr_dying_descendants are protected
+	 * by cgroup_mutex and css_set_lock. It's fine to read them holding
+	 * any of cgroup_mutex and css_set_lock; for writing both locks
+	 * should be held.
 	 */
 	int nr_descendants;
 	int nr_dying_descendants;
kernel/cgroup/cgroup.c +6 -0
index b230afccf635..7438c24297d4 100644
@@ -4758,9 +4758,11 @@ static void css_release_work_fn(struct work_struct *work)
 		if (cgroup_on_dfl(cgrp))
 			cgroup_rstat_flush(cgrp);
 
+		spin_lock_irq(&css_set_lock);
 		for (tcgrp = cgroup_parent(cgrp); tcgrp;
 		     tcgrp = cgroup_parent(tcgrp))
 			tcgrp->nr_dying_descendants--;
+		spin_unlock_irq(&css_set_lock);
 
 		cgroup_idr_remove(&cgrp->root->cgroup_idr, cgrp->id);
 		cgrp->id = -1;
@@ -4978,12 +4980,14 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
 	if (ret)
 		goto out_psi_free;
 
+	spin_lock_irq(&css_set_lock);
 	for (tcgrp = cgrp; tcgrp; tcgrp = cgroup_parent(tcgrp)) {
 		cgrp->ancestor_ids[tcgrp->level] = tcgrp->id;
 
 		if (tcgrp != cgrp)
 			tcgrp->nr_descendants++;
 	}
+	spin_unlock_irq(&css_set_lock);
 
 	if (notify_on_release(parent))
 		set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
@@ -5268,10 +5272,12 @@ static int cgroup_destroy_locked(struct cgroup *cgrp)
 	if (parent && cgroup_is_threaded(cgrp))
 		parent->nr_threaded_children--;
 
+	spin_lock_irq(&css_set_lock);
 	for (tcgrp = cgroup_parent(cgrp); tcgrp; tcgrp = cgroup_parent(tcgrp)) {
 		tcgrp->nr_descendants--;
 		tcgrp->nr_dying_descendants++;
 	}
+	spin_unlock_irq(&css_set_lock);
 
 	cgroup1_check_for_release(parent);
 
Signature · 1 line
2.20.1
#4 lore raw

PATCH v8 4/7 cgroup: cgroup v2 freezer

In reply to Roman Gushchin

Cgroup v1 implements the freezer controller, which provides an ability to stop the workload in a cgroup and temporarily free up some resources (cpu, io, network bandwidth and, potentially, memory) for some other tasks. Cgroup v2 lacks this functionality.

This patch implements freezer for cgroup v2.

Cgroup v2 freezer tries to put tasks into a state similar to jobctl stop. This means that tasks can be killed, ptraced (using PTRACE_SEIZE*), and interrupted. It is possible to attach to a frozen task, get some information (e.g. read registers) and detach. It's also possible to migrate a frozen tasks to another cgroup.

This differs cgroup v2 freezer from cgroup v1 freezer, which mostly tried to imitate the system-wide freezer. However uninterruptible sleep is fine when all tasks are going to be frozen (hibernation case), it's not the acceptable state for some subset of the system.

Cgroup v2 freezer is not supporting freezing kthreads.
If a non-root cgroup contains kthread, the cgroup still can be frozen, but the kthread will remain running, the cgroup will be shown as non-frozen, and the notification will not be delivered.

* PTRACE_ATTACH is not working because non-fatal signal delivery is blocked in frozen state.

There are some interface differences between cgroup v1 and cgroup v2 freezer too, which are required to conform the cgroup v2 interface design principles:
1) There is no separate controller, which has to be turned on:
the functionality is always available and is represented by cgroup.freeze and cgroup.events cgroup control files.
2) The desired state is defined by the cgroup.freeze control file. Any hierarchical configuration is allowed.
3) The interface is asynchronous. The actual state is available using cgroup.events control file ("frozen" field). There are no dedicated transitional states.
4) It's allowed to make any changes with the cgroup hierarchy (create new cgroups, remove old cgroups, move tasks between cgroups) no matter if some cgroups are frozen.

---
 include/linux/cgroup-defs.h  |  28 ++++
 include/linux/cgroup.h       |  42 ++++++
 include/linux/sched.h        |   2 +
 include/linux/sched/jobctl.h |   2 +
 kernel/cgroup/Makefile       |   2 +-
 kernel/cgroup/cgroup.c       | 103 ++++++++++++-
 kernel/cgroup/freezer.c      | 281 +++++++++++++++++++++++++++++++++++
 kernel/signal.c              |  84 ++++++++++-
 8 files changed, 533 insertions(+), 11 deletions(-)
 create mode 100644 kernel/cgroup/freezer.c
include/linux/cgroup-defs.h +28 -0
index 319c07305500..29c94850cfa7 100644
@@ -64,6 +64,12 @@ enum {
 	 * specified at mount time and thus is implemented here.
 	 */
 	CGRP_CPUSET_CLONE_CHILDREN,
+
+	/* Control group has to be frozen. */
+	CGRP_FREEZE,
+
+	/* Cgroup is frozen. */
+	CGRP_FROZEN,
 };
 
 /* cgroup_root->flags */
@@ -316,6 +322,25 @@ struct cgroup_rstat_cpu {
 	struct cgroup *updated_next;		/* NULL iff not on the list */
 };
 
+struct cgroup_freezer_state {
+	/* Should the cgroup and its descendants be frozen. */
+	bool freeze;
+
+	/* Should the cgroup actually be frozen? */
+	int e_freeze;
+
+	/* Fields below are protected by css_set_lock */
+
+	/* Number of frozen descendant cgroups */
+	int nr_frozen_descendants;
+
+	/*
+	 * Number of tasks, which are counted as frozen:
+	 * frozen, SIGSTOPped, and PTRACEd.
+	 */
+	int nr_stopped_tasks;
+};
+
 struct cgroup {
 	/* self css with NULL ->ss, points back to this cgroup */
 	struct cgroup_subsys_state self;
@@ -452,6 +477,9 @@ struct cgroup {
 	/* If there is block congestion on this cgroup. */
 	atomic_t congestion_count;
 
+	/* Used to store internal freezer state */
+	struct cgroup_freezer_state freezer;
+
 	/* ids of the ancestors at each level including self */
 	int ancestor_ids[];
 };
include/linux/cgroup.h +42 -0

46 lines not on this page · raw message

include/linux/sched.h +2 -0
index d2f90fa92468..825da92d0280 100644
@@ -733,6 +733,8 @@ struct task_struct {
 #ifdef CONFIG_CGROUPS
 	/* disallow userland-initiated cgroup migration */
 	unsigned			no_cgroup_migration:1;
+	/* task is frozen/stopped (used by the cgroup freezer) */
+	unsigned			stopped:1;
 #endif
 #ifdef CONFIG_BLK_CGROUP
 	/* to be used once the psi infrastructure lands upstream. */
include/linux/sched/jobctl.h +2 -0
index 98228bd48aee..fa067de9f1a9 100644
@@ -18,6 +18,7 @@ struct task_struct;
 #define JOBCTL_TRAP_NOTIFY_BIT	20	/* trap for NOTIFY */
 #define JOBCTL_TRAPPING_BIT	21	/* switching to TRACED */
 #define JOBCTL_LISTENING_BIT	22	/* ptracer is listening for events */
+#define JOBCTL_TRAP_FREEZE_BIT	23	/* trap for cgroup freezer */
 
 #define JOBCTL_STOP_DEQUEUED	(1UL << JOBCTL_STOP_DEQUEUED_BIT)
 #define JOBCTL_STOP_PENDING	(1UL << JOBCTL_STOP_PENDING_BIT)
@@ -26,6 +27,7 @@ struct task_struct;
 #define JOBCTL_TRAP_NOTIFY	(1UL << JOBCTL_TRAP_NOTIFY_BIT)
 #define JOBCTL_TRAPPING		(1UL << JOBCTL_TRAPPING_BIT)
 #define JOBCTL_LISTENING	(1UL << JOBCTL_LISTENING_BIT)
+#define JOBCTL_TRAP_FREEZE	(1UL << JOBCTL_TRAP_FREEZE_BIT)
 
 #define JOBCTL_TRAP_MASK	(JOBCTL_TRAP_STOP | JOBCTL_TRAP_NOTIFY)
 #define JOBCTL_PENDING_MASK	(JOBCTL_STOP_PENDING | JOBCTL_TRAP_MASK)
kernel/cgroup/Makefile +1 -1
index 8d5689ca94b9..5d7a76bfbbb7 100644
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o
+obj-y := cgroup.o rstat.o namespace.o cgroup-v1.o freezer.o
 
 obj-$(CONFIG_CGROUP_FREEZER) += legacy_freezer.o
 obj-$(CONFIG_CGROUP_PIDS) += pids.o
kernel/cgroup/cgroup.c +99 -4

157 lines not on this page · raw message

kernel/cgroup/freezer.c new file +281 -0

281 lines not on this page · raw message

kernel/signal.c +78 -6

141 lines not on this page · raw message

Signature · 1 line
2.20.1
#5 lore raw

Re: [PATCH v8 4/7] cgroup: cgroup v2 freezer

Quoted 10 lines · Roman Gushchin
kernel/signal.c +1 -0
@@ -2065,6 +2066,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t
 		preempt_disable();
 		read_unlock(&tasklist_lock);
 		preempt_enable_no_resched();
+		cgroup_enter_stopped();
 		freezable_schedule();
 	} else {
 		/*

kernel/signal.c static bool do_signal_stop(int signr) · line 2250

Quoted 6 lines
kernel/signal.c +1 -0
@@ -2243,6 +2245,7 @@ static bool do_signal_stop(int signr)
 		}
 
 		/* Now we don't run again until woken by SIGCONT or SIGKILL */
+		cgroup_enter_stopped();
 		freezable_schedule();
 		return true;

Please see my reply to 0/7.

kernel/signal.c static void do_jobctl_trap(void) · line 2291

Quoted 8 lines
kernel/signal.c +2 -1
@@ -2280,7 +2286,8 @@ static void do_jobctl_trap(void)
 		    !(signal->flags & SIGNAL_STOP_STOPPED))
 			signr = SIGTRAP;
 		WARN_ON_ONCE(!signr);
-		ptrace_do_notify(signr, signr | (PTRACE_EVENT_STOP << 8),
+		ptrace_do_notify(signr,
+				 signr | (PTRACE_EVENT_STOP << 8),
 				 CLD_STOPPED);

white-space cleanup?

kernel/signal.c bool get_signal(struct ksignal *ksig) · line 2423

Quoted 7 lines
kernel/signal.c +2 -1
@@ -2363,7 +2419,8 @@ bool get_signal(struct ksignal *ksig)
 	 * we should notify the parent, prepare_signal(SIGCONT) encodes
 	 * the CLD_ si_code into SIGNAL_CLD_MASK bits.
 	 */
-	if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
+	if (unlikely(signal->flags & SIGNAL_CLD_MASK) &
+	    !(current->jobctl & JOBCTL_TRAP_FREEZE)) {

Hmm, why?

Oleg.

#6 lore raw

Re: [PATCH v8 4/7] cgroup: cgroup v2 freezer

Quoted 13 lines · Oleg Nesterov
Quoted 12 lines · Roman Gushchin
kernel/signal.c +1 -0
@@ -2065,6 +2066,7 @@ static void ptrace_stop(int exit_code, int why, int clear_code, kernel_siginfo_t
 		preempt_disable();
 		read_unlock(&tasklist_lock);
 		preempt_enable_no_resched();
+		cgroup_enter_stopped();
 		freezable_schedule();
 	} else {
 		/*
@@ -2243,6 +2245,7 @@ static bool do_signal_stop(int signr)
 		}
 
Quoted 6 lines

kernel/signal.c static bool do_signal_stop(int signr) · line 2250

Quoted 4 lines
kernel/signal.c +1 -0
 		/* Now we don't run again until woken by SIGCONT or SIGKILL */
+		cgroup_enter_stopped();
 		freezable_schedule();
 		return true;

Please see my reply to 0/7.

Answered in that thread.

Quoted 11 lines

kernel/signal.c static void do_jobctl_trap(void) · line 2291

Quoted 9 lines
kernel/signal.c +2 -1
@@ -2280,7 +2286,8 @@ static void do_jobctl_trap(void)
 		    !(signal->flags & SIGNAL_STOP_STOPPED))
 			signr = SIGTRAP;
 		WARN_ON_ONCE(!signr);
-		ptrace_do_notify(signr, signr | (PTRACE_EVENT_STOP << 8),
+		ptrace_do_notify(signr,
+				 signr | (PTRACE_EVENT_STOP << 8),
 				 CLD_STOPPED);

white-space cleanup?

Sorry, a bogus change. Please, ignore it.

Quoted 10 lines

kernel/signal.c bool get_signal(struct ksignal *ksig) · line 2423

Quoted 8 lines
kernel/signal.c +2 -1
@@ -2363,7 +2419,8 @@ bool get_signal(struct ksignal *ksig)
 	 * we should notify the parent, prepare_signal(SIGCONT) encodes
 	 * the CLD_ si_code into SIGNAL_CLD_MASK bits.
 	 */
-	if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
+	if (unlikely(signal->flags & SIGNAL_CLD_MASK) &
+	    !(current->jobctl & JOBCTL_TRAP_FREEZE)) {

Hmm, why?

If a process is going from the stopped to the frozen state, it looks like it's better to delay parent's notification up to the moment when it will actually run.

Thanks!

#7 lore raw

Re: [PATCH v8 4/7] cgroup: cgroup v2 freezer

Quoted 14 lines · Roman Gushchin
Quoted 11 lines · Oleg Nesterov

kernel/signal.c bool get_signal(struct ksignal *ksig) · line 2423

Quoted 8 lines · Roman Gushchin
kernel/signal.c +2 -1
@@ -2363,7 +2419,8 @@ bool get_signal(struct ksignal *ksig)
 	 * we should notify the parent, prepare_signal(SIGCONT) encodes
 	 * the CLD_ si_code into SIGNAL_CLD_MASK bits.
 	 */
-	if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
+	if (unlikely(signal->flags & SIGNAL_CLD_MASK) &
+	    !(current->jobctl & JOBCTL_TRAP_FREEZE)) {

Hmm, why?

Quoted 3 lines

If a process is going from the stopped to the frozen state, it looks like it's better to delay parent's notification up to the moment when it will actually run.

Perhaps I missed something but I do not understand why it is really better, at least why it deserves this (small but still) complication...

OK, if nothing else, suppose that SIGCONT races with cgroup_do_freeze(true). With or without this change CLD_CONTINUED can be reported or not depending on /dev/random, do we really care?

Oleg.

#8 lore raw

PATCH v8 5/7 kselftests: cgroup: don't fail on cg_kill_all() error in cg_destroy()

In reply to Roman Gushchin

If the cgroup destruction races with an exit() of a belonging process(es), cg_kill_all() may fail. It's not a good reason to make cg_destroy() fail and leave the cgroup in place, potentially causing next test runs to fail.

---
 tools/testing/selftests/cgroup/cgroup_util.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
tools/testing/selftests/cgroup/cgroup_util.c +1 -3
index 14c9fe284806..eba06f94433b 100644
@@ -227,9 +227,7 @@ int cg_destroy(const char *cgroup)
 retry:
 	ret = rmdir(cgroup);
 	if (ret && errno == EBUSY) {
-		ret = cg_killall(cgroup);
-		if (ret)
-			return ret;
+		cg_killall(cgroup);
 		usleep(100);
 		goto retry;
 	}
Signature · 1 line
2.20.1
#9 lore raw

PATCH v8 6/7 kselftests: cgroup: add freezer controller self-tests

In reply to Roman Gushchin

This patch implements 8 tests for the freezer controller for cgroup v2:
1) a simple test, which aims to freeze and unfreeze a cgroup with 100 processes
2) a more complicated tree test, which creates a hierarchy of cgroups, puts some processes in some cgroups, and tries to freeze and unfreeze different parts of the subtree
3) a forkbomb test: the test aims to freeze a forkbomb running in a cgroup, kill all tasks in the cgroup and remove the cgroup without the unfreezing.
4) rmdir test: the test creates two nested cgroups, freezes the parent one, checks that the child can be successfully removed, and a new child can be created
5) migration tests: the test checks migration of a task between frozen cgroups: from a frozen to a running, from a running to a frozen, and from a frozen to a frozen.
6) ptrace test: the test checks that it's possible to attach to a process in a frozen cgroup, get some information and detach, and the cgroup will remain frozen.
7) stopped test: the test checks that it's possible to freeze a cgroup with a stopped task
8) ptraced test: the test checks that it's possible to freeze a cgroup with a ptraced task

Expected output:

  $ ./test_freezer
  ok 1 test_cgfreezer_simple
  ok 2 test_cgfreezer_tree
  ok 3 test_cgfreezer_forkbomb
  ok 4 test_cgrreezer_rmdir
  ok 5 test_cgfreezer_migrate
  ok 6 test_cgfreezer_ptrace
  ok 7 test_cgfreezer_stopped
  ok 8 test_cgfreezer_ptraced
---
 tools/testing/selftests/cgroup/.gitignore     |   1 +
 tools/testing/selftests/cgroup/Makefile       |   2 +
 tools/testing/selftests/cgroup/cgroup_util.c  |  81 +-
 tools/testing/selftests/cgroup/cgroup_util.h  |   7 +
 tools/testing/selftests/cgroup/test_freezer.c | 842 ++++++++++++++++++
 5 files changed, 932 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/cgroup/test_freezer.c
tools/testing/selftests/cgroup/.gitignore +1 -0
index adacda50a4b2..7f9835624793 100644
@@ -1,2 +1,3 @@
 test_memcontrol
 test_core
+test_freezer
tools/testing/selftests/cgroup/Makefile +2 -0
index 23fbaa4a9630..8d369b6a2069 100644
@@ -5,8 +5,10 @@ all:
 
 TEST_GEN_PROGS = test_memcontrol
 TEST_GEN_PROGS += test_core
+TEST_GEN_PROGS += test_freezer
 
 include ../lib.mk
 
 $(OUTPUT)/test_memcontrol: cgroup_util.c
 $(OUTPUT)/test_core: cgroup_util.c
+$(OUTPUT)/test_freezer: cgroup_util.c
tools/testing/selftests/cgroup/cgroup_util.c +80 -1

102 lines not on this page · raw message

tools/testing/selftests/cgroup/cgroup_util.h +7 -0
index 9ac8b7958f83..8ee63c00a668 100644
@@ -18,6 +18,7 @@ static inline int values_close(long a, long b, int err)
 extern int cg_find_unified_root(char *root, size_t len);
 extern char *cg_name(const char *root, const char *name);
 extern char *cg_name_indexed(const char *root, const char *name, int index);
+extern char *cg_control(const char *cgroup, const char *control);
 extern int cg_create(const char *cgroup);
 extern int cg_destroy(const char *cgroup);
 extern int cg_read(const char *cgroup, const char *control,
@@ -32,6 +33,7 @@ extern int cg_write(const char *cgroup, const char *control, char *buf);
 extern int cg_run(const char *cgroup,
 		  int (*fn)(const char *cgroup, void *arg),
 		  void *arg);
+extern int cg_enter(const char *cgroup, int pid);
 extern int cg_enter_current(const char *cgroup);
 extern int cg_run_nowait(const char *cgroup,
 			 int (*fn)(const char *cgroup, void *arg),
@@ -41,3 +43,8 @@ extern int alloc_pagecache(int fd, size_t size);
 extern int alloc_anon(const char *cgroup, void *arg);
 extern int is_swap_enabled(void);
 extern int set_oom_adj_score(int pid, int score);
+extern int cg_for_all_procs(const char *cgroup, int (*fn)(int pid, void *arg),
+			    void *arg);
+extern int cg_wait_for_proc_count(const char *cgroup, int count);
+extern int cg_killall(const char *cgroup);
+extern char proc_read_text(int pid, const char *item, char *buf, size_t size);
tools/testing/selftests/cgroup/test_freezer.c new file +842 -0

842 lines not on this page · raw message

Signature · 1 line
2.20.1
#10 lore raw

PATCH v8 7/7 cgroup: document cgroup v2 freezer interface

In reply to Roman Gushchin

Describe cgroup v2 freezer interface in the cgroup v2 admin guide.

---
 Documentation/admin-guide/cgroup-v2.rst | 27 +++++++++++++++++++++++++
 1 file changed, 27 insertions(+)
Documentation/admin-guide/cgroup-v2.rst +27 -0
index 61f8bbb0a1b2..78f078ddbe9c 100644
@@ -864,6 +864,8 @@ All cgroup core files are prefixed with "cgroup."
 	  populated
 		1 if the cgroup or its descendants contains any live
 		processes; otherwise, 0.
+	  frozen
+		1 if the cgroup is frozen; otherwise, 0.
 
   cgroup.max.descendants
 	A read-write single value files.  The default is "max".
@@ -897,6 +899,31 @@ All cgroup core files are prefixed with "cgroup."
 		A dying cgroup can consume system resources not exceeding
 		limits, which were active at the moment of cgroup deletion.
 
+  cgroup.freeze
+	A read-write single value file which exists on non-root cgroups.
+	Allowed values are "0" and "1". The default is "0".
+
+	Writing "1" to the file causes freezing of the cgroup and all
+	descendant cgroups. This means that all belonging processes will
+	be stopped and will not run until the cgroup will be explicitly
+	unfrozen. Freezing of the cgroup may take some time; when this action
+	is completed, the "frozen" value in the cgroup.events control file
+	will be updated to "1" and the corresponding notification will be
+	issued.
+
+	A cgroup can be frozen either by its own settings, or by settings
+	of any ancestor cgroups. If any of ancestor cgroups is frozen, the
+	cgroup will remain frozen.
+
+	Processes in the frozen cgroup can be killed by a fatal signal.
+	They also can enter and leave a frozen cgroup: either by an explicit
+	move by a user, or if freezing of the cgroup races with fork().
+	If a process is moved to a frozen cgroup, it stops. If a process is
+	moved out of a frozen cgroup, it becomes running.
+
+	Frozen status of a cgroup doesn't affect any cgroup tree operations:
+	it's possible to delete a frozen (and empty) cgroup, as well as
+	create new sub-cgroups.
 
 Controllers
 ===========
Signature · 1 line
2.20.1
#11 lore raw
In reply to Roman Gushchin
Quoted 4 lines · Roman Gushchin

It provides similar functionality as v1 freezer, but the interface conforms to the cgroup v2 interface design principles, and it provides a better user experience: tasks can be killed, ptrace works,

I tried to not argue with intent, but to be honest I am more and more sceptical... Lets forget about ptrace for the moment.

Once again, why do we want a killable freezer?

If a user wants to kill a frozen task from CGRP_FROZEN cgroup he can simply

1. send SIGKILL to that task

2. migrate it to the root cgroup.

why this doesn't / can't work?

Why I am starting to argue... The ability to kill a frozen task complicates the code, and since cgroup_enter_stopped() (in this version at least) doesn't properly interacts with freezable_schedule() leads to other problems.

From 7/7:

+ cgroup.freeze
+ A read-write single value file which exists on non-root cgroups.
+ Allowed values are "0" and "1". The default is "0".
+
+ Writing "1" to the file causes freezing of the cgroup and all
+ descendant cgroups. This means that all belonging processes will
+ be stopped and will not run until the cgroup will be explicitly
+ unfrozen. Freezing of the cgroup may take some time;
^^^^^^^^^^^^^^^^^^
it may take infinite time.

Just suppose that a task does vfork() and this races with cgroup_do_freeze(true). If the new child notices JOBCTL_TRAP_FREEZE before exit/exec the cgroup will be never frozen.

If I read the current kernel/cgroup/freezer.c correctly, CGROUP_FREEZING should "always" work (unless a task hangs in D state) and to me this looks more important than kill/ptrace support...

Quoted 1 line

there is no separate controller, which has to be enabled, etc.

agreed, this is nice.

Oleg.

#12 lore raw
Quoted 11 lines · Oleg Nesterov
Quoted 4 lines · Roman Gushchin

It provides similar functionality as v1 freezer, but the interface conforms to the cgroup v2 interface design principles, and it provides a better user experience: tasks can be killed, ptrace works,

I tried to not argue with intent, but to be honest I am more and more sceptical... Lets forget about ptrace for the moment.

Once again, why do we want a killable freezer?

Quoted 7 lines

If a user wants to kill a frozen task from CGRP_FROZEN cgroup he can simply

1. send SIGKILL to that task

2. migrate it to the root cgroup.

why this doesn't / can't work?

It does work, but it doesn't look as a nice interface to take into the cgroup v2 world.

It just not clear, why killing a frozen task requires some cgroup-level operations? It doesn't add anything except some additional complexity to the userspace. Generally speaking, any process hanging in D-state for a long time isn't the nicest object from the userspace's point of view.

Exactly as a SIGSTOPped process can be killed without sending SIGCONT, why a frozen task would require some additional operations?

And I'm not talking about the case, when the process which is sending SIGKILL has no write access to cgroupfs.

Quoted 17 lines

Why I am starting to argue... The ability to kill a frozen task complicates the code, and since cgroup_enter_stopped() (in this version at least) doesn't properly interacts with freezable_schedule() leads to other problems.

From 7/7:

+ cgroup.freeze
+ A read-write single value file which exists on non-root cgroups.
+ Allowed values are "0" and "1". The default is "0".
+
+ Writing "1" to the file causes freezing of the cgroup and all
+ descendant cgroups. This means that all belonging processes will
+ be stopped and will not run until the cgroup will be explicitly
+ unfrozen. Freezing of the cgroup may take some time;
^^^^^^^^^^^^^^^^^^
it may take infinite time.

Quoted 3 lines

Just suppose that a task does vfork() and this races with cgroup_do_freeze(true). If the new child notices JOBCTL_TRAP_FREEZE before exit/exec the cgroup will be never frozen.

Hm, why? cgroup_update_frozen() called from cgroup_post_fork() should bring the cgroup into the frozen state. If it's not true (I'm missing some race here), it's a bug, but I don't see why it's not possible in general.

Quoted 4 lines

If I read the current kernel/cgroup/freezer.c correctly, CGROUP_FREEZING should "always" work (unless a task hangs in D state) and to me this looks more important than kill/ptrace support...

Again, I don't see a case, when cgroup v1 freezer will work and the proposed v2 freezer won't work in general.

Quoted 4 lines
Quoted 2 lines

there is no separate controller, which has to be enabled, etc.

agreed, this is nice.

Thanks!

#13 lore raw
Quoted 14 lines · Roman Gushchin
Quoted 12 lines · Oleg Nesterov

I tried to not argue with intent, but to be honest I am more and more sceptical... Lets forget about ptrace for the moment.

Once again, why do we want a killable freezer?

If a user wants to kill a frozen task from CGRP_FROZEN cgroup he can simply

1. send SIGKILL to that task

2. migrate it to the root cgroup.

Quoted 8 lines
Quoted 1 line

why this doesn't / can't work?

It does work, but it doesn't look as a nice interface to take into the cgroup v2 world.

It just not clear, why killing a frozen task requires some cgroup-level operations? It doesn't add anything except some additional complexity to the userspace.

Yes.

But to me this is a reasonable trade-off because this way we do not add additional complexity to the kernel.

Actually, "killable" is not that difficult afaics. "ptraceable" looks more problematic to me. Again, user-space can do

1. PTRACE_SEIZE
2. move the tracee to the root cgroup
3. do anything with the tracee
4. move it back

Quoted 2 lines

Generally speaking, any process hanging in D-state
for a long time isn't the nicest object from the userspace's point of view.

Roman, this is unfair comparison ;)

Quoted 2 lines

Exactly as a SIGSTOPped process can be killed without sending SIGCONT, why a frozen task would require some additional operations?

this too,

Quoted 2 lines

And I'm not talking about the case, when the process which is sending SIGKILL has no write access to cgroupfs.

True.

But there is another case. If admin wants to freeze a cgroup then it is not clear why a user which can send SIGKILL to a frozen process should wake it up.

------------------------------------------------------------------------------
Again, it is not that I hate the idea of killable/ptraceable freezer. Just I personally think it's not worth the trouble. Perhaps I am wrong, but so far I do not see a good implementation...

And, apart from reading/writing the registers, what can ptrace do with a frozen tracee? This doesn't look like a "must have" feature to me.

At least, may I ask you again to make (if possible) a separate patch which adds the ability to kill/ptrace?
------------------------------------------------------------------------------

Quoted 17 lines
Quoted 17 lines

Why I am starting to argue... The ability to kill a frozen task complicates the code, and since cgroup_enter_stopped() (in this version at least) doesn't properly interacts with freezable_schedule() leads to other problems.

From 7/7:

+ cgroup.freeze
+ A read-write single value file which exists on non-root cgroups.
+ Allowed values are "0" and "1". The default is "0".
+
+ Writing "1" to the file causes freezing of the cgroup and all
+ descendant cgroups. This means that all belonging processes will
+ be stopped and will not run until the cgroup will be explicitly
+ unfrozen. Freezing of the cgroup may take some time;
^^^^^^^^^^^^^^^^^^
it may take infinite time.

Quoted 7 lines
Quoted 3 lines

Just suppose that a task does vfork() and this races with cgroup_do_freeze(true). If the new child notices JOBCTL_TRAP_FREEZE before exit/exec the cgroup will be never frozen.

Hm, why? cgroup_update_frozen() called from cgroup_post_fork() should bring the cgroup into the frozen state. If it's not true (I'm missing some race here), it's a bug, but I don't see why it's not possible in general.

A task P calls vfork() and creates the new child C. Now, how can the parent P (which sleeps in TASK_KILLABLE) call cgroup_enter_stopped() ? It can't until C exits or execs. C can't exit or exec because it is frozen.

Quoted 6 lines
Quoted 3 lines

If I read the current kernel/cgroup/freezer.c correctly, CGROUP_FREEZING should "always" work (unless a task hangs in D state) and to me this looks more important than kill/ptrace support...

Again, I don't see a case, when cgroup v1 freezer will work and the proposed v2 freezer won't work in general.

See above.

Oleg.

maintainer
#14 lore raw

Hello, Oleg.

Quoted 2 lines · Oleg Nesterov

But to me this is a reasonable trade-off because this way we do not add additional complexity to the kernel.

So, I really wanna avoid allowing userspace to cause D state sleeps. It's not impossible to work around but becomes really nasty. For example, imagine a memory pressure based userspace oom handler issuer kills based on per-cgroup pressure metric (as oomd would do). It might not necessarily have the insight that a victim cgroup is frozen or whether it can move out its members to a different cgroup (in a lot of cases that will cause a lot of confusion in management software).

The frozen state in cgroup1 is a new task state which is different from all others and in a nasty way and it has been causing various confusions and mistakes from its users. We really should make it closer to the existing stop behaviors even if that means more complexity in the implementation.

Quoted 7 lines

Actually, "killable" is not that difficult afaics. "ptraceable" looks more problematic to me. Again, user-space can do

1. PTRACE_SEIZE
2. move the tracee to the root cgroup
3. do anything with the tracee
4. move it back

which is fine. The goal isn't trying to block userspace from doing things that it explicitly wants to do. That's fine. We just want things like killing and ptracing to behave similarly to other stopped states (ie. avoid introducing completely new behaviors).

Quoted 8 lines

But there is another case. If admin wants to freeze a cgroup then it is not clear why a user which can send SIGKILL to a frozen process should wake it up.

------------------------------------------------------------------------------
Again, it is not that I hate the idea of killable/ptraceable freezer. Just I personally think it's not worth the trouble. Perhaps I am wrong, but so far I do not see a good implementation...

Quoted 5 lines

And, apart from reading/writing the registers, what can ptrace do with a frozen tracee? This doesn't look like a "must have" feature to me.

At least, may I ask you again to make (if possible) a separate patch which adds the ability to kill/ptrace?

ptrace support is a lot less important than kill for sure but if at all possible I think it'd be better to have it because that makes the frozen state closer to other stopped states and thus less surprising.

To summarize, the ideal result is the frozen state to be "stuck in jobctl stop loop" but except for looping in it no different from regular jobctl stop. Plus, register state examination is already useful for frozen cgroups for debbugging purposes in itself.

Thanks.

Signature · 1 line
tejun
#15 lore raw

Hi,

Quoted 2 lines · Tejun Heo

So, I really wanna avoid allowing userspace to cause D state sleeps.

...

Quoted 2 lines

ptrace support is a lot less important than kill for sure but if at all possible I think it'd be better to have it

Tejun, I agree it would be better. I did not argue with that.

The question is how this can be implemented. And how much uglifications^W complications in the core kernel code this needs.

Quoted 2 lines

To summarize, the ideal result is the frozen state to be "stuck in jobctl stop loop"

Not sure I understand, but I don't think this can work...

Let me repeat, imo the freezer should be "reliable", it shouldn't stuck in CGRP_FREEZE state forever if, say, it races with vfork(). And personally I think this is more important than (very limited) ptrace support.

So I think it too should somehow interact with freezable_schedule/etc.

Oleg.

maintainer
#16 lore raw

Hey, Oleg.

Quoted 7 lines · Oleg Nesterov
Quoted 2 lines

ptrace support is a lot less important than kill for sure but if at all possible I think it'd be better to have it

Tejun, I agree it would be better. I did not argue with that.

The question is how this can be implemented. And how much uglifications^W complications in the core kernel code this needs.

Yeah, sure thing. It's always a trade-off but given that this is something we're gonna expose to userspace as an API which will be difficult to change too noticeably once released, I think it'd be worthwhile to spend some effort to get the visible semantics right.

Quoted 8 lines
Quoted 2 lines

To summarize, the ideal result is the frozen state to be "stuck in jobctl stop loop"

Not sure I understand, but I don't think this can work...

Let me repeat, imo the freezer should be "reliable", it shouldn't stuck in CGRP_FREEZE state forever if, say, it races with vfork(). And personally I think this is more important than (very limited) ptrace support.

Absolutely, it makes no sense to ship it otherwise. I don't think anyone is saying that we can ship it while it doesn't work reliably.

Quoted 1 line

So I think it too should somehow interact with freezable_schedule/etc.

You mean freezer_do_not_count(), right? As long as the task is guaranteed to be trapped by signal stop afterwards (and they are), we likely can use them the same way. The only thing to be careful about would be ensuring that we don't end up flipping group level frozen state inbetween. Would something like that work?

Thanks.

Signature · 1 line
tejun
#17 lore raw
Quoted 4 lines · Tejun Heo
Quoted 2 lines

So I think it too should somehow interact with freezable_schedule/etc.

You mean freezer_do_not_count(), right?

yes,

Quoted 5 lines

As long as the task is
guaranteed to be trapped by signal stop afterwards (and they are), we likely can use them the same way. The only thing to be careful about would be ensuring that we don't end up flipping group level frozen state inbetween. Would something like that work?

I have no idea because I do not understand what exactly do you mean ;)

However. Thinking more about this, I am not sure my concerns were valid. Yes, cg freezer can "hang" if it races with vfork(). But probably we should blame vfork(), not freezer.

The problem is, even ^Z can "hang" if the foreground process does vfork() and the new child stops before exit/exec. Now I recall that I even tried to make a patch to fix this using ERESTART_RESTARTBLOCK, but had some nasty problems with blocked signals...

de_thread() should use freezable_schedule() in TASK_KILLABLE too. Currently it doesn't, but only because we have other (much more serious) problems with cred_guard_mutex/exec. However, this is is fine wrt cg freezer, other threads can't be frozen exactly because it is killable.

Anything else does freezer_do_not_count() in TASK_KILLABLE and waits for another freezable process?

So it seems I have to take my words back, perhaps we can forget about freezable_schedule/etc.

Oleg.

maintainer
#18 lore raw

Hello, Oleg.

Sorry about the delay.

Quoted 7 lines · Oleg Nesterov
Quoted 5 lines

As long as the task is
guaranteed to be trapped by signal stop afterwards (and they are), we likely can use them the same way. The only thing to be careful about would be ensuring that we don't end up flipping group level frozen state inbetween. Would something like that work?

I have no idea because I do not understand what exactly do you mean ;)

Heh, sorry about that. What I meant was that we can consider a task which is blocked in vfork wait as already frozen and that if we do so we need to be careful so that frozen state doesn't do a flip between vfork wait ending and the task getting parked again in a jobctl stop.

Quoted 3 lines

However. Thinking more about this, I am not sure my concerns were valid. Yes, cg freezer can "hang" if it races with vfork(). But probably we should blame vfork(), not freezer.

I think we'd need to cover that ground regardless of where blame lies. It's weird if freezing doesn't complete cuz one of the tasks messed up while vforking.

Quoted 4 lines

The problem is, even ^Z can "hang" if the foreground process does vfork() and the new child stops before exit/exec. Now I recall that I even tried to make a patch to fix this using ERESTART_RESTARTBLOCK, but had some nasty problems with blocked signals...

Ugh... yeah, these wait non-interruptible wait sites which can be exposed to userspace are nasty. They end up adding a unique wait state visible to userspace which comes with a bunch of corner cases.

Quoted 7 lines

de_thread() should use freezable_schedule() in TASK_KILLABLE too. Currently it doesn't, but only because we have other (much more serious) problems with cred_guard_mutex/exec. However, this is is fine wrt cg freezer, other threads can't be frozen exactly because it is killable.

Anything else does freezer_do_not_count() in TASK_KILLABLE and waits for another freezable process?

Can't find any. Hopefully, that was it?

Quoted 2 lines

So it seems I have to take my words back, perhaps we can forget about freezable_schedule/etc.

I think it'd be great to be able to handle these if at all possible.

Thanks.

Signature · 1 line
tejun
#19 lore raw
In reply to Oleg Nesterov
Quoted 37 lines · Oleg Nesterov
Quoted 14 lines · Roman Gushchin
Quoted 12 lines · Oleg Nesterov

I tried to not argue with intent, but to be honest I am more and more sceptical... Lets forget about ptrace for the moment.

Once again, why do we want a killable freezer?

If a user wants to kill a frozen task from CGRP_FROZEN cgroup he can simply

1. send SIGKILL to that task

2. migrate it to the root cgroup.

Quoted 8 lines
Quoted 1 line

why this doesn't / can't work?

It does work, but it doesn't look as a nice interface to take into the cgroup v2 world.

It just not clear, why killing a frozen task requires some cgroup-level operations? It doesn't add anything except some additional complexity to the userspace.

Yes.

But to me this is a reasonable trade-off because this way we do not add additional complexity to the kernel.

Actually, "killable" is not that difficult afaics. "ptraceable" looks more problematic to me. Again, user-space can do

1. PTRACE_SEIZE
2. move the tracee to the root cgroup
3. do anything with the tracee
4. move it back

Quoted 4 lines
Quoted 2 lines

Generally speaking, any process hanging in D-state
for a long time isn't the nicest object from the userspace's point of view.

Roman, this is unfair comparison ;)

Why not? This is exactly the point, with v1 freezer you get a task in D state, which isn't manageable by userspace without some actions with sysfs.

Quoted 6 lines
Quoted 3 lines

Exactly as a SIGSTOPped process can be killed without sending SIGCONT, why a frozen task would require some additional operations?

this too,

Quoted 7 lines
Quoted 2 lines

And I'm not talking about the case, when the process which is sending SIGKILL has no write access to cgroupfs.

True.

But there is another case. If admin wants to freeze a cgroup then it is not clear why a user which can send SIGKILL to a frozen process should wake it up.

But it will woken up only for a short moment. And the cgroup will remain frozen in a sense that belonging processes do not consume CPU.
Actually, a signal can be useful: for example, if it was the last process, the container management software might want to delete the cgroup.

Quoted 8 lines

------------------------------------------------------------------------------
Again, it is not that I hate the idea of killable/ptraceable freezer. Just I personally think it's not worth the trouble. Perhaps I am wrong, but so far I do not see a good implementation...

And, apart from reading/writing the registers, what can ptrace do with a frozen tracee? This doesn't look like a "must have" feature to me.

I think the minimal requirement is that the tracing application should not hang and wait for tracee to be unfrozen.
So, imagine you're trying to debug an application in production with gdb, and occasionally gdb just hangs because some cluster management stuff froze the tracee's cgroup. Not the best user experience.

Quoted 3 lines

At least, may I ask you again to make (if possible) a separate patch which adds the ability to kill/ptrace?

I'll try, but not sure if it can make the code easier for review. It looks like this ability defines the implementation.

Quoted 23 lines

------------------------------------------------------------------------------

Quoted 21 lines
Quoted 20 lines

Why I am starting to argue... The ability to kill a frozen task complicates the code, and since cgroup_enter_stopped() (in this version at least) doesn't properly interacts with freezable_schedule() leads to other problems.

From 7/7:

+ cgroup.freeze
+ A read-write single value file which exists on non-root cgroups.
+ Allowed values are "0" and "1". The default is "0".
+
+ Writing "1" to the file causes freezing of the cgroup and all
+ descendant cgroups. This means that all belonging processes will
+ be stopped and will not run until the cgroup will be explicitly
+ unfrozen. Freezing of the cgroup may take some time;
^^^^^^^^^^^^^^^^^^
it may take infinite time.

Just suppose that a task does vfork() and this races with cgroup_do_freeze(true). If the new child notices JOBCTL_TRAP_FREEZE before exit/exec the cgroup will be never frozen.

Quoted 7 lines
Quoted 3 lines

Hm, why? cgroup_update_frozen() called from cgroup_post_fork() should bring the cgroup into the frozen state. If it's not true (I'm missing some race here), it's a bug, but I don't see why it's not possible in general.

A task P calls vfork() and creates the new child C. Now, how can the parent P (which sleeps in TASK_KILLABLE) call cgroup_enter_stopped() ? It can't until C exits or execs. C can't exit or exec because it is frozen.

Got it. I'll address it in the next version.

Thanks!

#20 lore raw
Quoted 7 lines · Roman Gushchin
Quoted 5 lines
Quoted 3 lines

Generally speaking, any process hanging in D-state
for a long time isn't the nicest object from the userspace's point of view.

Roman, this is unfair comparison ;)

Why not?

OK, you are trolling me, let me troll you back...

So, generally speaking, the very idea of freezer looks wrong, any process hanging in do_freezer_trap() for a long time isn't the nicest object from the userspace's point of view.

Quoted 8 lines
Quoted 2 lines

And, apart from reading/writing the registers, what can ptrace do with a frozen tracee? This doesn't look like a "must have" feature to me.

I think the minimal requirement is that the tracing application should not hang and wait for tracee to be unfrozen.
So, imagine you're trying to debug an application in production with gdb, and occasionally gdb just hangs because some cluster management stuff froze the tracee's cgroup. Not the best user experience.

Firstly, gdb will likely hang anyway. Say, single-step will hang and ^C won't work.

Secondly, just imagine you're trying to debug an application in production with gdb, and occasionally gdb just hangs because some cluster management stuff froze the gdb's cgroup. Not the best user experience.

Roman, may be it was not clear, but I never said that ptrace/kill makes no sense. But yes, we probably disagree about how much this is important. I won't really argue, but so far I am not sure I understand how this can be implemented.

Quoted 5 lines
Quoted 2 lines

At least, may I ask you again to make (if possible) a separate patch which adds the ability to kill/ptrace?

I'll try, but not sure if it can make the code easier for review. It looks like this ability defines the implementation.

OK, I won't insist, I understand that this is not simple.

Oleg.