Merge pull request #23764 from enr0n/oomd-allow-managed-oom-preference
oomd: allow ManagedOOMPreference on all cgroups
This commit is contained in:
@@ -1077,9 +1077,20 @@ DeviceAllow=/dev/loop-control
|
||||
<para>Allows deprioritizing or omitting this unit's cgroup as a candidate when
|
||||
<command>systemd-oomd</command> needs to act. Requires support for extended attributes (see
|
||||
<citerefentry project='man-pages'><refentrytitle>xattr</refentrytitle><manvolnum>7</manvolnum></citerefentry>)
|
||||
in order to use <option>avoid</option> or <option>omit</option>. Additionally,
|
||||
<command>systemd-oomd</command> will ignore these extended attributes if the unit's cgroup is not
|
||||
owned by the root user.</para>
|
||||
in order to use <option>avoid</option> or <option>omit</option>.</para>
|
||||
|
||||
<para>When calculating candidates to relieve swap usage, <command>systemd-oomd</command> will
|
||||
only respect these extended attributes if the unit's cgroup is owned by root.</para>
|
||||
|
||||
<para>When calculating candidates to relieve memory pressure, <command>systemd-oomd</command>
|
||||
will only respect these extended attributes if the unit's cgroup owner, and the
|
||||
owner of the monitored ancestor cgroup are the same. For example, if <command>systemd-oomd</command>
|
||||
is calculating candidates for <filename>-.slice</filename>, then extended attributes set
|
||||
on descendants of <filename>/user.slice/user-1000.slice/user@1000.service/</filename>
|
||||
will be ignored because the descendants are owned by UID 1000, and <filename>-.slice</filename>
|
||||
is owned by UID 0. But, if calculating candidates for
|
||||
<filename>/user.slice/user-1000.slice/user@1000.service/</filename>, then extended attributes set
|
||||
on the descendants would be respected.</para>
|
||||
|
||||
<para>If this property is set to <option>avoid</option>, the service manager will convey this to
|
||||
<command>systemd-oomd</command>, which will only select this cgroup if there are no other viable
|
||||
|
||||
+3
-2
@@ -783,6 +783,9 @@ static void cgroup_xattr_apply(Unit *u) {
|
||||
|
||||
assert(u);
|
||||
|
||||
/* The 'user.*' xattrs can be set from a user manager. */
|
||||
cgroup_oomd_xattr_apply(u, u->cgroup_path);
|
||||
|
||||
if (!MANAGER_IS_SYSTEM(u->manager))
|
||||
return;
|
||||
|
||||
@@ -809,8 +812,6 @@ static void cgroup_xattr_apply(Unit *u) {
|
||||
else
|
||||
unit_remove_xattr_graceful(u, NULL, xn);
|
||||
}
|
||||
|
||||
cgroup_oomd_xattr_apply(u, u->cgroup_path);
|
||||
}
|
||||
|
||||
static int lookup_block_device(const char *p, dev_t *ret) {
|
||||
|
||||
+54
-21
@@ -14,6 +14,7 @@
|
||||
#include "sort-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "user-util.h"
|
||||
|
||||
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
|
||||
oomd_cgroup_ctx_hash_ops,
|
||||
@@ -142,10 +143,53 @@ bool oomd_swap_free_below(const OomdSystemContext *ctx, int threshold_permyriad)
|
||||
return (ctx->swap_total - ctx->swap_used) < swap_threshold;
|
||||
}
|
||||
|
||||
int oomd_fetch_cgroup_oom_preference(OomdCGroupContext *ctx, const char *prefix) {
|
||||
uid_t uid, prefix_uid;
|
||||
int r;
|
||||
|
||||
assert(ctx);
|
||||
|
||||
prefix = empty_to_root(prefix);
|
||||
|
||||
if (!path_startswith(ctx->path, prefix))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s is not a descendant of %s", ctx->path, prefix);
|
||||
|
||||
r = cg_get_owner(SYSTEMD_CGROUP_CONTROLLER, ctx->path, &uid);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to get owner/group from %s: %m", ctx->path);
|
||||
|
||||
r = cg_get_owner(SYSTEMD_CGROUP_CONTROLLER, prefix, &prefix_uid);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to get owner/group from %s: %m", ctx->path);
|
||||
|
||||
if (uid == prefix_uid) {
|
||||
/* Ignore most errors when reading the xattr since it is usually unset and cgroup xattrs are only used
|
||||
* as an optional feature of systemd-oomd (and the system might not even support them). */
|
||||
r = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, ctx->path, "user.oomd_avoid");
|
||||
if (r == -ENOMEM)
|
||||
return log_oom_debug();
|
||||
if (r < 0 && r != -ENODATA)
|
||||
log_debug_errno(r, "Failed to get xattr user.oomd_avoid, ignoring: %m");
|
||||
ctx->preference = r > 0 ? MANAGED_OOM_PREFERENCE_AVOID : ctx->preference;
|
||||
|
||||
r = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, ctx->path, "user.oomd_omit");
|
||||
if (r == -ENOMEM)
|
||||
return log_oom_debug();
|
||||
if (r < 0 && r != -ENODATA)
|
||||
log_debug_errno(r, "Failed to get xattr user.oomd_omit, ignoring: %m");
|
||||
ctx->preference = r > 0 ? MANAGED_OOM_PREFERENCE_OMIT : ctx->preference;
|
||||
} else
|
||||
ctx->preference = MANAGED_OOM_PREFERENCE_NONE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int oomd_sort_cgroup_contexts(Hashmap *h, oomd_compare_t compare_func, const char *prefix, OomdCGroupContext ***ret) {
|
||||
_cleanup_free_ OomdCGroupContext **sorted = NULL;
|
||||
OomdCGroupContext *item;
|
||||
size_t k = 0;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert(compare_func);
|
||||
@@ -157,7 +201,14 @@ int oomd_sort_cgroup_contexts(Hashmap *h, oomd_compare_t compare_func, const cha
|
||||
|
||||
HASHMAP_FOREACH(item, h) {
|
||||
/* Skip over cgroups that are not valid candidates or are explicitly marked for omission */
|
||||
if ((item->path && prefix && !path_startswith(item->path, prefix)) || item->preference == MANAGED_OOM_PREFERENCE_OMIT)
|
||||
if (item->path && prefix && !path_startswith(item->path, prefix))
|
||||
continue;
|
||||
|
||||
r = oomd_fetch_cgroup_oom_preference(item, prefix);
|
||||
if (r == -ENOMEM)
|
||||
return r;
|
||||
|
||||
if (item->preference == MANAGED_OOM_PREFERENCE_OMIT)
|
||||
continue;
|
||||
|
||||
sorted[k++] = item;
|
||||
@@ -272,7 +323,7 @@ int oomd_kill_by_pgscan_rate(Hashmap *h, const char *prefix, bool dry_run, char
|
||||
continue; /* Try to find something else to kill */
|
||||
}
|
||||
|
||||
dump_until = MAX(dump_until, i);
|
||||
dump_until = MAX(dump_until, i + 1);
|
||||
char *selected = strdup(sorted[i]->path);
|
||||
if (!selected)
|
||||
return -ENOMEM;
|
||||
@@ -316,7 +367,7 @@ int oomd_kill_by_swap_usage(Hashmap *h, uint64_t threshold_usage, bool dry_run,
|
||||
continue; /* Try to find something else to kill */
|
||||
}
|
||||
|
||||
dump_until = MAX(dump_until, i);
|
||||
dump_until = MAX(dump_until, i + 1);
|
||||
char *selected = strdup(sorted[i]->path);
|
||||
if (!selected)
|
||||
return -ENOMEM;
|
||||
@@ -334,7 +385,6 @@ int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) {
|
||||
_cleanup_(oomd_cgroup_context_freep) OomdCGroupContext *ctx = NULL;
|
||||
_cleanup_free_ char *p = NULL, *val = NULL;
|
||||
bool is_root;
|
||||
uid_t uid;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
@@ -355,23 +405,6 @@ int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) {
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Error parsing memory pressure from %s: %m", p);
|
||||
|
||||
r = cg_get_owner(SYSTEMD_CGROUP_CONTROLLER, path, &uid);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to get owner/group from %s: %m", path);
|
||||
else if (uid == 0) {
|
||||
/* Ignore most errors when reading the xattr since it is usually unset and cgroup xattrs are only used
|
||||
* as an optional feature of systemd-oomd (and the system might not even support them). */
|
||||
r = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, path, "user.oomd_avoid");
|
||||
if (r == -ENOMEM)
|
||||
return r;
|
||||
ctx->preference = r == 1 ? MANAGED_OOM_PREFERENCE_AVOID : ctx->preference;
|
||||
|
||||
r = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, path, "user.oomd_omit");
|
||||
if (r == -ENOMEM)
|
||||
return r;
|
||||
ctx->preference = r == 1 ? MANAGED_OOM_PREFERENCE_OMIT : ctx->preference;
|
||||
}
|
||||
|
||||
if (is_root) {
|
||||
r = procfs_memory_get_used(&ctx->current_memory_usage);
|
||||
if (r < 0)
|
||||
|
||||
@@ -109,6 +109,15 @@ static inline int compare_swap_usage(OomdCGroupContext * const *c1, OomdCGroupCo
|
||||
* Returns the number of sorted items; negative on error. */
|
||||
int oomd_sort_cgroup_contexts(Hashmap *h, oomd_compare_t compare_func, const char *prefix, OomdCGroupContext ***ret);
|
||||
|
||||
/* If the cgroups represented by `ctx` and `prefix` are owned by the same user,
|
||||
* then set `ctx->preference` using the `user.oomd_avoid` and `user.oomd_omit`
|
||||
* xattrs. Otherwise, set `ctx->preference` to MANAGED_OOM_PREFERENCE_NONE.
|
||||
*
|
||||
* If `prefix` is NULL or the empty string, it is treated as root. If `prefix`
|
||||
* does not specify an ancestor cgroup of `ctx`, -EINVAL is returned. Returns
|
||||
* negative on all other errors. */
|
||||
int oomd_fetch_cgroup_oom_preference(OomdCGroupContext *ctx, const char *prefix);
|
||||
|
||||
/* Returns a negative value on error, 0 if no processes were killed, or 1 if processes were killed. */
|
||||
int oomd_cgroup_kill(const char *path, bool recurse, bool dry_run);
|
||||
|
||||
|
||||
+83
-45
@@ -94,11 +94,8 @@ static void test_oomd_cgroup_context_acquire_and_insert(void) {
|
||||
_cleanup_hashmap_free_ Hashmap *h1 = NULL, *h2 = NULL;
|
||||
_cleanup_(oomd_cgroup_context_freep) OomdCGroupContext *ctx = NULL;
|
||||
_cleanup_free_ char *cgroup = NULL;
|
||||
ManagedOOMPreference root_pref;
|
||||
OomdCGroupContext *c1, *c2;
|
||||
CGroupMask mask;
|
||||
bool test_xattrs;
|
||||
int root_xattrs, r;
|
||||
|
||||
if (geteuid() != 0)
|
||||
return (void) log_tests_skipped("not root");
|
||||
@@ -115,17 +112,6 @@ static void test_oomd_cgroup_context_acquire_and_insert(void) {
|
||||
return (void) log_tests_skipped("cgroup memory controller is not available");
|
||||
|
||||
assert_se(cg_pid_get_path(NULL, 0, &cgroup) >= 0);
|
||||
|
||||
/* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities
|
||||
* so skip the xattr portions of the test. */
|
||||
r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_test", "1", 1, 0);
|
||||
test_xattrs = !ERRNO_IS_PRIVILEGE(r) && !ERRNO_IS_NOT_SUPPORTED(r);
|
||||
|
||||
if (test_xattrs) {
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "1", 1, 0) >= 0);
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
|
||||
}
|
||||
|
||||
assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
|
||||
|
||||
assert_se(streq(ctx->path, cgroup));
|
||||
@@ -135,34 +121,11 @@ static void test_oomd_cgroup_context_acquire_and_insert(void) {
|
||||
assert_se(ctx->swap_usage == 0);
|
||||
assert_se(ctx->last_pgscan == 0);
|
||||
assert_se(ctx->pgscan == 0);
|
||||
/* omit takes precedence over avoid when both are set to true */
|
||||
if (test_xattrs)
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_OMIT);
|
||||
else
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
|
||||
ctx = oomd_cgroup_context_free(ctx);
|
||||
|
||||
/* also check when only avoid is set to true */
|
||||
if (test_xattrs) {
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "0", 1, 0) >= 0);
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
|
||||
}
|
||||
assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
|
||||
if (test_xattrs)
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_AVOID);
|
||||
ctx = oomd_cgroup_context_free(ctx);
|
||||
|
||||
/* Test the root cgroup */
|
||||
/* Root cgroup is live and not made on demand like the cgroup the test runs in. It can have varying
|
||||
* xattrs set already so let's read in the booleans first to get the final preference value. */
|
||||
root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_omit");
|
||||
root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_OMIT : MANAGED_OOM_PREFERENCE_NONE;
|
||||
root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_avoid");
|
||||
root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_AVOID : MANAGED_OOM_PREFERENCE_NONE;
|
||||
assert_se(oomd_cgroup_context_acquire("", &ctx) == 0);
|
||||
assert_se(streq(ctx->path, "/"));
|
||||
assert_se(ctx->current_memory_usage > 0);
|
||||
assert_se(ctx->preference == root_pref);
|
||||
|
||||
/* Test hashmap inserts */
|
||||
assert_se(h1 = hashmap_new(&oomd_cgroup_ctx_hash_ops));
|
||||
@@ -187,14 +150,6 @@ static void test_oomd_cgroup_context_acquire_and_insert(void) {
|
||||
assert_se(c2->mem_pressure_limit == 6789);
|
||||
assert_se(c2->mem_pressure_limit_hit_start == 42);
|
||||
assert_se(c2->last_had_mem_reclaim == 888); /* assumes the live pgscan is less than UINT64_MAX */
|
||||
|
||||
/* Assert that avoid/omit are not set if the cgroup is not owned by root */
|
||||
if (test_xattrs) {
|
||||
ctx = oomd_cgroup_context_free(ctx);
|
||||
assert_se(cg_set_access(SYSTEMD_CGROUP_CONTROLLER, cgroup, 65534, 0) >= 0);
|
||||
assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_oomd_update_cgroup_contexts_between_hashmaps(void) {
|
||||
@@ -451,6 +406,88 @@ static void test_oomd_sort_cgroups(void) {
|
||||
sorted_cgroups = mfree(sorted_cgroups);
|
||||
}
|
||||
|
||||
static void test_oomd_fetch_cgroup_oom_preference(void) {
|
||||
_cleanup_(oomd_cgroup_context_freep) OomdCGroupContext *ctx = NULL;
|
||||
_cleanup_free_ char *cgroup = NULL;
|
||||
ManagedOOMPreference root_pref;
|
||||
CGroupMask mask;
|
||||
bool test_xattrs;
|
||||
int root_xattrs, r;
|
||||
|
||||
if (geteuid() != 0)
|
||||
return (void) log_tests_skipped("not root");
|
||||
|
||||
if (!is_pressure_supported())
|
||||
return (void) log_tests_skipped("system does not support pressure");
|
||||
|
||||
if (cg_all_unified() <= 0)
|
||||
return (void) log_tests_skipped("cgroups are not running in unified mode");
|
||||
|
||||
assert_se(cg_mask_supported(&mask) >= 0);
|
||||
|
||||
if (!FLAGS_SET(mask, CGROUP_MASK_MEMORY))
|
||||
return (void) log_tests_skipped("cgroup memory controller is not available");
|
||||
|
||||
assert_se(cg_pid_get_path(NULL, 0, &cgroup) >= 0);
|
||||
assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
|
||||
|
||||
/* If we don't have permissions to set xattrs we're likely in a userns or missing capabilities
|
||||
* so skip the xattr portions of the test. */
|
||||
r = cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_test", "1", 1, 0);
|
||||
test_xattrs = !ERRNO_IS_PRIVILEGE(r) && !ERRNO_IS_NOT_SUPPORTED(r);
|
||||
|
||||
if (test_xattrs) {
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, NULL) == 0);
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "1", 1, 0) >= 0);
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
|
||||
|
||||
/* omit takes precedence over avoid when both are set to true */
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, NULL) == 0);
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_OMIT);
|
||||
} else {
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, NULL) < 0);
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
|
||||
}
|
||||
ctx = oomd_cgroup_context_free(ctx);
|
||||
|
||||
/* also check when only avoid is set to true */
|
||||
if (test_xattrs) {
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_omit", "0", 1, 0) >= 0);
|
||||
assert_se(cg_set_xattr(SYSTEMD_CGROUP_CONTROLLER, cgroup, "user.oomd_avoid", "1", 1, 0) >= 0);
|
||||
assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, NULL) == 0);
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_AVOID);
|
||||
ctx = oomd_cgroup_context_free(ctx);
|
||||
}
|
||||
|
||||
/* Test the root cgroup */
|
||||
/* Root cgroup is live and not made on demand like the cgroup the test runs in. It can have varying
|
||||
* xattrs set already so let's read in the booleans first to get the final preference value. */
|
||||
assert_se(oomd_cgroup_context_acquire("", &ctx) == 0);
|
||||
root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_omit");
|
||||
root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_OMIT : MANAGED_OOM_PREFERENCE_NONE;
|
||||
root_xattrs = cg_get_xattr_bool(SYSTEMD_CGROUP_CONTROLLER, "", "user.oomd_avoid");
|
||||
root_pref = root_xattrs > 0 ? MANAGED_OOM_PREFERENCE_AVOID : MANAGED_OOM_PREFERENCE_NONE;
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, NULL) == 0);
|
||||
assert_se(ctx->preference == root_pref);
|
||||
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, "/herp.slice/derp.scope") == -EINVAL);
|
||||
|
||||
/* Assert that avoid/omit are not set if the cgroup and prefix are not
|
||||
* owned by the same user.*/
|
||||
if (test_xattrs && !empty_or_root(ctx->path)) {
|
||||
ctx = oomd_cgroup_context_free(ctx);
|
||||
assert_se(cg_set_access(SYSTEMD_CGROUP_CONTROLLER, cgroup, 65534, 0) >= 0);
|
||||
assert_se(oomd_cgroup_context_acquire(cgroup, &ctx) == 0);
|
||||
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, NULL) == 0);
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_NONE);
|
||||
|
||||
assert_se(oomd_fetch_cgroup_oom_preference(ctx, ctx->path) == 0);
|
||||
assert_se(ctx->preference == MANAGED_OOM_PREFERENCE_AVOID);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int r;
|
||||
|
||||
@@ -470,6 +507,7 @@ int main(void) {
|
||||
|
||||
test_oomd_cgroup_kill();
|
||||
test_oomd_cgroup_context_acquire_and_insert();
|
||||
test_oomd_fetch_cgroup_oom_preference();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user