Skip to content

Commit

Permalink
Merge pull request #13512 from msekletar/freezer
Browse files Browse the repository at this point in the history
core: introduce support for cgroup freezer
  • Loading branch information
keszybz committed May 1, 2020
2 parents cad6727 + d446ae8 commit b76ef59
Show file tree
Hide file tree
Showing 27 changed files with 926 additions and 24 deletions.
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ CHANGES WITH 246 in spe:
their first output column with --no-legend. To hide the first column,
use --plain.

* The service manager gained basic support for cgroup v2 freezer. Units
can now be suspended or resumed either using new systemctl verbs,
freeze and thaw respectively, or via D-Bus.

CHANGES WITH 245:

* A new tool "systemd-repart" has been added, that operates as an
Expand Down
24 changes: 24 additions & 0 deletions man/systemctl.xml
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,30 @@ Sun 2017-02-26 20:57:49 EST 2h 3min left Sun 2017-02-26 11:56:36 EST 6h ago
generally redundant and reproducible on the next invocation of the unit).</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>freeze <replaceable>PATTERN</replaceable>…</command></term>

<listitem>
<para>Freeze one or more units specified on the
command line using cgroup freezer</para>

<para>Freezing the unit will cause all processes contained within the cgroup corresponding to the unit
to be suspended. Being suspended means that unit's processes won't be scheduled to run on CPU until thawed.
Note that this command is supported only on systems that use unified cgroup hierarchy. Unit is automatically
thawed just before we execute a job against the unit, e.g. before the unit is stopped.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>thaw <replaceable>PATTERN</replaceable>…</command></term>

<listitem>
<para>Thaw (unfreeze) one or more units specified on the
command line.</para>

<para>This is the inverse operation to the <command>freeze</command> command and resumes the execution of
processes in the unit's cgroup.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>is-active <replaceable>PATTERN</replaceable>…</command></term>

Expand Down
25 changes: 22 additions & 3 deletions src/basic/cgroup-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ bool cg_ns_supported(void) {
return enabled;
}

bool cg_freezer_supported(void) {
static thread_local int supported = -1;

if (supported >= 0)
return supported;

supported = cg_all_unified() > 0 && access("/sys/fs/cgroup/init.scope/cgroup.freeze", F_OK) == 0;

return supported;
}

int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) {
_cleanup_free_ char *fs = NULL;
int r;
Expand Down Expand Up @@ -1684,12 +1695,13 @@ int cg_get_attribute_as_uint64(const char *controller, const char *path, const c
return 0;
}

int cg_get_keyed_attribute(
int cg_get_keyed_attribute_full(
const char *controller,
const char *path,
const char *attribute,
char **keys,
char **ret_values) {
char **ret_values,
CGroupKeyMode mode) {

_cleanup_free_ char *filename = NULL, *contents = NULL;
const char *p;
Expand All @@ -1701,7 +1713,8 @@ int cg_get_keyed_attribute(
* all keys to retrieve. The 'ret_values' parameter should be passed as string size with the same number of
* entries as 'keys'. On success each entry will be set to the value of the matching key.
*
* If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. */
* If the attribute file doesn't exist at all returns ENOENT, if any key is not found returns ENXIO. If mode
* is set to GG_KEY_MODE_GRACEFUL we ignore missing keys and return those that were parsed successfully. */

r = cg_get_path(controller, path, attribute, &filename);
if (r < 0)
Expand Down Expand Up @@ -1749,6 +1762,9 @@ int cg_get_keyed_attribute(
p += strspn(p, NEWLINE);
}

if (mode & CG_KEY_MODE_GRACEFUL)
goto done;

r = -ENXIO;

fail:
Expand All @@ -1759,6 +1775,9 @@ int cg_get_keyed_attribute(

done:
memcpy(ret_values, v, sizeof(char*) * n);
if (mode & CG_KEY_MODE_GRACEFUL)
return n_done;

return 0;
}

Expand Down
25 changes: 24 additions & 1 deletion src/basic/cgroup-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,31 @@ int cg_pid_get_path(const char *controller, pid_t pid, char **path);

int cg_rmdir(const char *controller, const char *path);

typedef enum {
CG_KEY_MODE_GRACEFUL = 1 << 0,
} CGroupKeyMode;

int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value);
int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret);
int cg_get_keyed_attribute(const char *controller, const char *path, const char *attribute, char **keys, char **values);
int cg_get_keyed_attribute_full(const char *controller, const char *path, const char *attribute, char **keys, char **values, CGroupKeyMode mode);

static inline int cg_get_keyed_attribute(
const char *controller,
const char *path,
const char *attribute,
char **keys,
char **ret_values) {
return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, 0);
}

static inline int cg_get_keyed_attribute_graceful(
const char *controller,
const char *path,
const char *attribute,
char **keys,
char **ret_values) {
return cg_get_keyed_attribute_full(controller, path, attribute, keys, ret_values, CG_KEY_MODE_GRACEFUL);
}

int cg_get_attribute_as_uint64(const char *controller, const char *path, const char *attribute, uint64_t *ret);

Expand Down Expand Up @@ -238,6 +260,7 @@ int cg_mask_to_string(CGroupMask mask, char **ret);
int cg_kernel_controllers(Set **controllers);

bool cg_ns_supported(void);
bool cg_freezer_supported(void);

int cg_all_unified(void);
int cg_hybrid_unified(void);
Expand Down
9 changes: 9 additions & 0 deletions src/basic/unit-def.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = {

DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);

static const char* const freezer_state_table[_FREEZER_STATE_MAX] = {
[FREEZER_RUNNING] = "running",
[FREEZER_FREEZING] = "freezing",
[FREEZER_FROZEN] = "frozen",
[FREEZER_THAWING] = "thawing",
};

DEFINE_STRING_TABLE_LOOKUP(freezer_state, FreezerState);

static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = {
[AUTOMOUNT_DEAD] = "dead",
[AUTOMOUNT_WAITING] = "waiting",
Expand Down
12 changes: 12 additions & 0 deletions src/basic/unit-def.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ typedef enum UnitActiveState {
_UNIT_ACTIVE_STATE_INVALID = -1
} UnitActiveState;

typedef enum FreezerState {
FREEZER_RUNNING,
FREEZER_FREEZING,
FREEZER_FROZEN,
FREEZER_THAWING,
_FREEZER_STATE_MAX,
_FREEZER_STATE_INVALID = -1
} FreezerState;

typedef enum AutomountState {
AUTOMOUNT_DEAD,
AUTOMOUNT_WAITING,
Expand Down Expand Up @@ -253,6 +262,9 @@ UnitLoadState unit_load_state_from_string(const char *s) _pure_;
const char *unit_active_state_to_string(UnitActiveState i) _const_;
UnitActiveState unit_active_state_from_string(const char *s) _pure_;

const char *freezer_state_to_string(FreezerState i) _const_;
FreezerState freezer_state_from_string(const char *s) _pure_;

const char* automount_state_to_string(AutomountState i) _const_;
AutomountState automount_state_from_string(const char *s) _pure_;

Expand Down
96 changes: 95 additions & 1 deletion src/core/cgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "io-util.h"
#include "limits-util.h"
#include "nulstr-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
Expand Down Expand Up @@ -2661,6 +2663,16 @@ void unit_add_to_cgroup_empty_queue(Unit *u) {
log_debug_errno(r, "Failed to enable cgroup empty event source: %m");
}

static void unit_remove_from_cgroup_empty_queue(Unit *u) {
assert(u);

if (!u->in_cgroup_empty_queue)
return;

LIST_REMOVE(cgroup_empty_queue, u->manager->cgroup_empty_queue, u);
u->in_cgroup_empty_queue = false;
}

int unit_check_oom(Unit *u) {
_cleanup_free_ char *oom_kill = NULL;
bool increased;
Expand Down Expand Up @@ -2761,6 +2773,41 @@ static void unit_add_to_cgroup_oom_queue(Unit *u) {
log_error_errno(r, "Failed to enable cgroup oom event source: %m");
}

static int unit_check_cgroup_events(Unit *u) {
char *values[2] = {};
int r;

assert(u);

r = cg_get_keyed_attribute_graceful(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events",
STRV_MAKE("populated", "frozen"), values);
if (r < 0)
return r;

/* The cgroup.events notifications can be merged together so act as we saw the given state for the
* first time. The functions we call to handle given state are idempotent, which makes them
* effectively remember the previous state. */
if (values[0]) {
if (streq(values[0], "1"))
unit_remove_from_cgroup_empty_queue(u);
else
unit_add_to_cgroup_empty_queue(u);
}

/* Disregard freezer state changes due to operations not initiated by us */
if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) {
if (streq(values[1], "0"))
unit_thawed(u);
else
unit_frozen(u);
}

free(values[0]);
free(values[1]);

return 0;
}

static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
Manager *m = userdata;

Expand Down Expand Up @@ -2797,7 +2844,7 @@ static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents,

u = hashmap_get(m->cgroup_control_inotify_wd_unit, INT_TO_PTR(e->wd));
if (u)
unit_add_to_cgroup_empty_queue(u);
unit_check_cgroup_events(u);

u = hashmap_get(m->cgroup_memory_inotify_wd_unit, INT_TO_PTR(e->wd));
if (u)
Expand Down Expand Up @@ -3550,6 +3597,46 @@ int compare_job_priority(const void *a, const void *b) {
return strcmp(x->unit->id, y->unit->id);
}

int unit_cgroup_freezer_action(Unit *u, FreezerAction action) {
_cleanup_free_ char *path = NULL;
FreezerState target, kernel = _FREEZER_STATE_INVALID;
int r;

assert(u);
assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW));

if (!u->cgroup_realized)
return -EBUSY;

target = action == FREEZER_FREEZE ? FREEZER_FROZEN : FREEZER_RUNNING;

r = unit_freezer_state_kernel(u, &kernel);
if (r < 0)
log_unit_debug_errno(u, r, "Failed to obtain cgroup freezer state: %m");

if (target == kernel) {
u->freezer_state = target;
return 0;
}

r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.freeze", &path);
if (r < 0)
return r;

log_unit_debug(u, "%s unit.", action == FREEZER_FREEZE ? "Freezing" : "Thawing");

if (action == FREEZER_FREEZE)
u->freezer_state = FREEZER_FREEZING;
else
u->freezer_state = FREEZER_THAWING;

r = write_string_file(path, one_zero(action == FREEZER_FREEZE), WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
return r;

return 0;
}

static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
[CGROUP_DEVICE_POLICY_AUTO] = "auto",
[CGROUP_DEVICE_POLICY_CLOSED] = "closed",
Expand Down Expand Up @@ -3585,3 +3672,10 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) {
}

DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy);

static const char* const freezer_action_table[_FREEZER_ACTION_MAX] = {
[FREEZER_FREEZE] = "freeze",
[FREEZER_THAW] = "thaw",
};

DEFINE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction);
12 changes: 12 additions & 0 deletions src/core/cgroup.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ typedef enum CGroupDevicePolicy {
_CGROUP_DEVICE_POLICY_INVALID = -1
} CGroupDevicePolicy;

typedef enum FreezerAction {
FREEZER_FREEZE,
FREEZER_THAW,

_FREEZER_ACTION_MAX,
_FREEZER_ACTION_INVALID = -1,
} FreezerAction;

struct CGroupDeviceAllow {
LIST_FIELDS(CGroupDeviceAllow, device_allow);
char *path;
Expand Down Expand Up @@ -274,3 +282,7 @@ bool unit_cgroup_delegate(Unit *u);
int compare_job_priority(const void *a, const void *b);

int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name);
int unit_cgroup_freezer_action(Unit *u, FreezerAction action);

const char* freezer_action_to_string(FreezerAction a) _const_;
FreezerAction freezer_action_from_string(const char *s) _pure_;
20 changes: 20 additions & 0 deletions src/core/dbus-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,14 @@ static int method_clean_unit(sd_bus_message *message, void *userdata, sd_bus_err
return method_generic_unit_operation(message, userdata, error, bus_unit_method_clean, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
}

static int method_freeze_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return method_generic_unit_operation(message, userdata, error, bus_unit_method_freeze, 0);
}

static int method_thaw_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
return method_generic_unit_operation(message, userdata, error, bus_unit_method_thaw, 0);
}

static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Don't load the unit (because unloaded units can't be in failed state), and don't insist on the
* unit to be loaded properly (since a failed unit might have its unit file disappeared) */
Expand Down Expand Up @@ -2584,6 +2592,18 @@ const sd_bus_vtable bus_manager_vtable[] = {
NULL,,
method_clean_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("FreezeUnit",
"s",
SD_BUS_PARAM(name),
NULL,,
method_freeze_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("ThawUnit",
"s",
SD_BUS_PARAM(name),
NULL,,
method_thaw_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("ResetFailedUnit",
"s",
SD_BUS_PARAM(name),
Expand Down

0 comments on commit b76ef59

Please sign in to comment.