803fb7
From 7d44d0d43465892d4753ff50592588f49d56cf95 Mon Sep 17 00:00:00 2001
803fb7
From: Lennart Poettering <lennart@poettering.net>
803fb7
Date: Thu, 10 Sep 2015 12:32:16 +0200
803fb7
Subject: [PATCH] core: add support for the "pids" cgroup controller
803fb7
803fb7
This adds support for the new "pids" cgroup controller of 4.3 kernels.
803fb7
It allows accounting the number of tasks in a cgroup and enforcing
803fb7
limits on it.
803fb7
803fb7
This adds two new setting TasksAccounting= and TasksMax= to each unit,
803fb7
as well as a gloabl option DefaultTasksAccounting=.
803fb7
803fb7
This also updated "cgtop" to optionally make use of the new
803fb7
kernel-provided accounting.
803fb7
803fb7
systemctl has been updated to show the number of tasks for each service
803fb7
if it is available.
803fb7
803fb7
This patch also adds correct support for undoing memory limits for units
803fb7
using a MemoryLimit=infinity syntax. We do the same for TasksMax= now
803fb7
and hence keep things in sync here.
803fb7
803fb7
Cherry-picked from: 03a7b521e3ffb7f5d153d90480ba5d4bc29d1e8f
803fb7
Resolves: #1337244
803fb7
---
803fb7
 man/systemd-system.conf.xml           | 22 ++++++------
803fb7
 man/systemd.resource-control.xml      | 63 ++++++++++++++++++++++++++++-------
803fb7
 src/core/cgroup.c                     | 44 ++++++++++++++++++++++++
803fb7
 src/core/cgroup.h                     |  5 +++
803fb7
 src/core/dbus-cgroup.c                | 41 ++++++++++++++++++++++-
803fb7
 src/core/dbus-unit.c                  | 25 ++++++++++++++
803fb7
 src/core/load-fragment-gperf.gperf.m4 |  2 ++
803fb7
 src/core/load-fragment.c              | 37 +++++++++++++++++---
803fb7
 src/core/load-fragment.h              |  1 +
803fb7
 src/core/main.c                       |  3 ++
803fb7
 src/core/manager.h                    |  1 +
803fb7
 src/core/unit.c                       |  1 +
803fb7
 src/shared/cgroup-util.h              |  1 +
803fb7
 src/systemctl/systemctl.c             | 17 ++++++++++
803fb7
 14 files changed, 236 insertions(+), 27 deletions(-)
803fb7
803fb7
diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml
803fb7
index 57b3b90be..d367ccd13 100644
803fb7
--- a/man/systemd-system.conf.xml
803fb7
+++ b/man/systemd-system.conf.xml
803fb7
@@ -51,14 +51,14 @@
803fb7
   </refnamediv>
803fb7
 
803fb7
   <refsynopsisdiv>
803fb7
-    <para><filename>/etc/systemd/system.conf</filename></para>
803fb7
-    <para><filename>/etc/systemd/system.conf.d/*.conf</filename></para>
803fb7
-    <para><filename>/run/systemd/system.conf.d/*.conf</filename></para>
803fb7
-    <para><filename>/usr/lib/systemd/system.conf.d/*.conf</filename></para>
803fb7
-    <para><filename>/etc/systemd/user.conf</filename></para>
803fb7
-    <para><filename>/etc/systemd/user.conf.d/*.conf</filename></para>
803fb7
-    <para><filename>/run/systemd/user.conf.d/*.conf</filename></para>
803fb7
-    <para><filename>/usr/lib/systemd/user.conf.d/*.conf</filename></para>
803fb7
+    <para><filename>/etc/systemd/system.conf</filename>,
803fb7
+    <filename>/etc/systemd/system.conf.d/*.conf</filename>,
803fb7
+    <filename>/run/systemd/system.conf.d/*.conf</filename>,
803fb7
+    <filename>/usr/lib/systemd/system.conf.d/*.conf</filename></para>
803fb7
+    <para><filename>/etc/systemd/user.conf</filename>,
803fb7
+    <filename>/etc/systemd/user.conf.d/*.conf</filename>,
803fb7
+    <filename>/run/systemd/user.conf.d/*.conf</filename>,
803fb7
+    <filename>/usr/lib/systemd/user.conf.d/*.conf</filename></para>
803fb7
   </refsynopsisdiv>
803fb7
 
803fb7
   <refsect1>
803fb7
@@ -307,12 +307,14 @@
803fb7
         <term><varname>DefaultCPUAccounting=</varname></term>
803fb7
         <term><varname>DefaultBlockIOAccounting=</varname></term>
803fb7
         <term><varname>DefaultMemoryAccounting=</varname></term>
803fb7
+        <term><varname>DefaultTasksAccounting=</varname></term>
803fb7
 
803fb7
         <listitem><para>Configure the default resource accounting
803fb7
         settings, as configured per-unit by
803fb7
         <varname>CPUAccounting=</varname>,
803fb7
-        <varname>BlockIOAccounting=</varname> and
803fb7
-        <varname>MemoryAccounting=</varname>. See
803fb7
+        <varname>BlockIOAccounting=</varname>,
803fb7
+        <varname>MemoryAccounting=</varname> and
803fb7
+        <varname>TasksAccounting=</varname>. See
803fb7
         <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
803fb7
         for details on the per-unit settings.</para></listitem>
803fb7
       </varlistentry>
803fb7
diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml
803fb7
index 8f4e7a3f1..6b9329bbe 100644
803fb7
--- a/man/systemd.resource-control.xml
803fb7
+++ b/man/systemd.resource-control.xml
803fb7
@@ -103,10 +103,10 @@
803fb7
         <listitem>
803fb7
           <para>Turn on CPU usage accounting for this unit. Takes a
803fb7
           boolean argument. Note that turning on CPU accounting for
803fb7
-          one unit might also implicitly turn it on for all units
803fb7
+          one unit will also implicitly turn it on for all units
803fb7
           contained in the same slice and for all its parent slices
803fb7
           and the units contained therein. The system default for this
803fb7
-          setting maybe controlled with
803fb7
+          setting may be controlled with
803fb7
           <varname>DefaultCPUAccounting=</varname> in
803fb7
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
803fb7
         </listitem>
803fb7
@@ -134,7 +134,7 @@
803fb7
           prioritizing specific services at boot-up differently than
803fb7
           during normal runtime.</para>
803fb7
 
803fb7
-          <para>Those options imply
803fb7
+          <para>These options imply
803fb7
           <literal>CPUAccounting=true</literal>.</para>
803fb7
         </listitem>
803fb7
       </varlistentry>
803fb7
@@ -168,9 +168,10 @@
803fb7
         <listitem>
803fb7
           <para>Turn on process and kernel memory accounting for this
803fb7
           unit. Takes a boolean argument. Note that turning on memory
803fb7
-          accounting for one unit might also implicitly turn it on for
803fb7
-          all its parent slices. The system default for this setting
803fb7
-          maybe controlled with
803fb7
+          accounting for one unit will also implicitly turn it on for
803fb7
+          all units contained in the same slice and for all its parent
803fb7
+          slices and the units contained therein. The system default
803fb7
+          for this setting may be controlled with
803fb7
           <varname>DefaultMemoryAccounting=</varname> in
803fb7
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
803fb7
         </listitem>
803fb7
@@ -186,26 +187,64 @@
803fb7
           memory size in bytes. If the value is suffixed with K, M, G
803fb7
           or T, the specified memory size is parsed as Kilobytes,
803fb7
           Megabytes, Gigabytes, or Terabytes (with the base 1024),
803fb7
-          respectively. This controls the
803fb7
-          <literal>memory.limit_in_bytes</literal> control group
803fb7
-          attribute. For details about this control group attribute,
803fb7
-          see 
803fb7
+          respectively. If assigned the special value
803fb7
+          <literal>infinity</literal> no memory limit is applied. This
803fb7
+          controls the <literal>memory.limit_in_bytes</literal>
803fb7
+          control group attribute. For details about this control
803fb7
+          group attribute, see 
803fb7
           url="https://www.kernel.org/doc/Documentation/cgroups/memory.txt">memory.txt</ulink>.</para>
803fb7
 
803fb7
           <para>Implies <literal>MemoryAccounting=true</literal>.</para>
803fb7
         </listitem>
803fb7
       </varlistentry>
803fb7
 
803fb7
+      <varlistentry>
803fb7
+        <term><varname>TasksAccounting=</varname></term>
803fb7
+
803fb7
+        <listitem>
803fb7
+          <para>Turn on task accounting for this unit. Takes a
803fb7
+          boolean argument. If enabled, the system manager will keep
803fb7
+          track of the number of tasks in the unit. The number of
803fb7
+          tasks accounted this way includes both kernel threads and
803fb7
+          userspace processes, with each thread counting
803fb7
+          individually. Note that turning on tasks accounting for one
803fb7
+          unit will also implicitly turn it on for all units contained
803fb7
+          in the same slice and for all its parent slices and the
803fb7
+          units contained therein. The system default for this setting
803fb7
+          may be controlled with
803fb7
+          <varname>DefaultTasksAccounting=</varname> in
803fb7
+          <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
803fb7
+        </listitem>
803fb7
+      </varlistentry>
803fb7
+
803fb7
+      <varlistentry>
803fb7
+        <term><varname>TasksMax=<replaceable>N</replaceable></varname></term>
803fb7
+
803fb7
+        <listitem>
803fb7
+          <para>Specify the maximum number of tasks that may be
803fb7
+          created in the unit. This ensures that the number of tasks
803fb7
+          accounted for the unit (see above) stays below a specific
803fb7
+          limit. If assigned the special value
803fb7
+          <literal>infinity</literal> no tasks limit is applied. This
803fb7
+          controls the <literal>pids.max</literal> control group
803fb7
+          attribute. For details about this control group attribute,
803fb7
+          see 
803fb7
+          url="https://www.kernel.org/doc/Documentation/cgroups/pids.txt">pids.txt</ulink>.</para>
803fb7
+
803fb7
+          <para>Implies <literal>TasksAccounting=true</literal>.</para>
803fb7
+        </listitem>
803fb7
+      </varlistentry>
803fb7
+
803fb7
       <varlistentry>
803fb7
         <term><varname>BlockIOAccounting=</varname></term>
803fb7
 
803fb7
         <listitem>
803fb7
           <para>Turn on Block IO accounting for this unit. Takes a
803fb7
           boolean argument. Note that turning on block IO accounting
803fb7
-          for one unit might also implicitly turn it on for all units
803fb7
+          for one unit will also implicitly turn it on for all units
803fb7
           contained in the same slice and all for its parent slices
803fb7
           and the units contained therein. The system default for this
803fb7
-          setting maybe controlled with
803fb7
+          setting may be controlled with
803fb7
           <varname>DefaultBlockIOAccounting=</varname> in
803fb7
           <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
803fb7
         </listitem>
803fb7
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
803fb7
index b7f08fb42..d4a8f9cbe 100644
803fb7
--- a/src/core/cgroup.c
803fb7
+++ b/src/core/cgroup.c
803fb7
@@ -40,6 +40,7 @@ void cgroup_context_init(CGroupContext *c) {
803fb7
         c->memory_limit = (uint64_t) -1;
803fb7
         c->blockio_weight = (unsigned long) -1;
803fb7
         c->startup_blockio_weight = (unsigned long) -1;
803fb7
+        c->tasks_max = (uint64_t) -1;
803fb7
 
803fb7
         c->cpu_quota_per_sec_usec = USEC_INFINITY;
803fb7
 }
803fb7
@@ -105,6 +106,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
803fb7
                 "%sBlockIOWeight=%lu\n"
803fb7
                 "%sStartupBlockIOWeight=%lu\n"
803fb7
                 "%sMemoryLimit=%" PRIu64 "\n"
803fb7
+                "%sTasksMax=%" PRIu64 "\n"
803fb7
                 "%sDevicePolicy=%s\n"
803fb7
                 "%sDelegate=%s\n",
803fb7
                 prefix, yes_no(c->cpu_accounting),
803fb7
@@ -116,6 +118,7 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
803fb7
                 prefix, c->blockio_weight,
803fb7
                 prefix, c->startup_blockio_weight,
803fb7
                 prefix, c->memory_limit,
803fb7
+                prefix, c->tasks_max,
803fb7
                 prefix, cgroup_device_policy_to_string(c->device_policy),
803fb7
                 prefix, yes_no(c->delegate));
803fb7
 
803fb7
@@ -456,6 +459,21 @@ void cgroup_context_apply(CGroupContext *c, CGroupControllerMask mask, const cha
803fb7
                                 log_debug("Ignoring device %s while writing cgroup attribute.", a->path);
803fb7
                 }
803fb7
         }
803fb7
+
803fb7
+        if ((mask & CGROUP_PIDS) && !is_root) {
803fb7
+
803fb7
+                if (c->tasks_max != (uint64_t) -1) {
803fb7
+                        char buf[DECIMAL_STR_MAX(uint64_t) + 2];
803fb7
+
803fb7
+                        sprintf(buf, "%" PRIu64 "\n", c->tasks_max);
803fb7
+                        r = cg_set_attribute("pids", path, "pids.max", buf);
803fb7
+                } else
803fb7
+                        r = cg_set_attribute("pids", path, "pids.max", "max");
803fb7
+
803fb7
+                if (r < 0)
803fb7
+                        log_full_errno(IN_SET(r, -ENOENT, -EROFS) ? LOG_DEBUG : LOG_WARNING, r,
803fb7
+                                       "Failed to set pids.max on %s: %m", path);
803fb7
+        }
803fb7
 }
803fb7
 
803fb7
 CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) {
803fb7
@@ -484,6 +502,10 @@ CGroupControllerMask cgroup_context_get_mask(CGroupContext *c) {
803fb7
             c->device_policy != CGROUP_AUTO)
803fb7
                 mask |= CGROUP_DEVICE;
803fb7
 
803fb7
+        if (c->tasks_accounting ||
803fb7
+            c->tasks_max != (uint64_t) -1)
803fb7
+                mask |= CGROUP_PIDS;
803fb7
+
803fb7
         return mask;
803fb7
 }
803fb7
 
803fb7
@@ -1044,6 +1066,28 @@ int manager_notify_cgroup_empty(Manager *m, const char *cgroup) {
803fb7
         return 0;
803fb7
 }
803fb7
 
803fb7
+int unit_get_tasks_current(Unit *u, uint64_t *ret) {
803fb7
+        _cleanup_free_ char *v = NULL;
803fb7
+        int r;
803fb7
+
803fb7
+        assert(u);
803fb7
+       assert(ret);
803fb7
+
803fb7
+        if (!u->cgroup_path)
803fb7
+                return -ENODATA;
803fb7
+
803fb7
+        if ((u->cgroup_realized_mask & CGROUP_PIDS) == 0)
803fb7
+                return -ENODATA;
803fb7
+
803fb7
+        r = cg_get_attribute("pids", u->cgroup_path, "pids.current", &v);
803fb7
+        if (r == -ENOENT)
803fb7
+                return -ENODATA;
803fb7
+        if (r < 0)
803fb7
+                return r;
803fb7
+
803fb7
+        return safe_atou64(v, ret);
803fb7
+}
803fb7
+
803fb7
 static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = {
803fb7
         [CGROUP_AUTO] = "auto",
803fb7
         [CGROUP_CLOSED] = "closed",
803fb7
diff --git a/src/core/cgroup.h b/src/core/cgroup.h
803fb7
index 8fa851de3..8af3eaa3a 100644
803fb7
--- a/src/core/cgroup.h
803fb7
+++ b/src/core/cgroup.h
803fb7
@@ -72,6 +72,7 @@ struct CGroupContext {
803fb7
         bool cpu_accounting;
803fb7
         bool blockio_accounting;
803fb7
         bool memory_accounting;
803fb7
+        bool tasks_accounting;
803fb7
 
803fb7
         unsigned long cpu_shares;
803fb7
         unsigned long startup_cpu_shares;
803fb7
@@ -88,6 +89,8 @@ struct CGroupContext {
803fb7
         LIST_HEAD(CGroupDeviceAllow, device_allow);
803fb7
 
803fb7
         bool delegate;
803fb7
+
803fb7
+        uint64_t tasks_max;
803fb7
 };
803fb7
 
803fb7
 #include "unit.h"
803fb7
@@ -127,5 +130,7 @@ pid_t unit_search_main_pid(Unit *u);
803fb7
 
803fb7
 int manager_notify_cgroup_empty(Manager *m, const char *group);
803fb7
 
803fb7
+int unit_get_tasks_current(Unit *u, uint64_t *ret);
803fb7
+
803fb7
 const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_;
803fb7
 CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_;
803fb7
diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c
803fb7
index 4a9df0601..a4465dc7a 100644
803fb7
--- a/src/core/dbus-cgroup.c
803fb7
+++ b/src/core/dbus-cgroup.c
803fb7
@@ -168,6 +168,8 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
803fb7
         SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0),
803fb7
         SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0),
803fb7
         SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0),
803fb7
+        SD_BUS_PROPERTY("TasksAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, tasks_accounting), 0),
803fb7
+        SD_BUS_PROPERTY("TasksMax", "t", NULL, offsetof(CGroupContext, tasks_max), 0),
803fb7
         SD_BUS_VTABLE_END
803fb7
 };
803fb7
 
803fb7
@@ -551,7 +553,11 @@ int bus_cgroup_set_property(
803fb7
                 if (mode != UNIT_CHECK) {
803fb7
                         c->memory_limit = limit;
803fb7
                         u->cgroup_realized_mask &= ~CGROUP_MEMORY;
803fb7
-                        unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64, name, limit);
803fb7
+
803fb7
+                        if (limit == (uint64_t) -1)
803fb7
+                                unit_write_drop_in_private(u, mode, name, "MemoryLimit=infinity");
803fb7
+                        else
803fb7
+                                unit_write_drop_in_private_format(u, mode, name, "MemoryLimit=%" PRIu64, limit);
803fb7
                 }
803fb7
 
803fb7
                 return 1;
803fb7
@@ -667,6 +673,39 @@ int bus_cgroup_set_property(
803fb7
 
803fb7
                 return 1;
803fb7
 
803fb7
+        } else if (streq(name, "TasksAccounting")) {
803fb7
+                int b;
803fb7
+
803fb7
+                r = sd_bus_message_read(message, "b", &b);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
+
803fb7
+                if (mode != UNIT_CHECK) {
803fb7
+                        c->tasks_accounting = b;
803fb7
+                        u->cgroup_realized_mask &= ~CGROUP_PIDS;
803fb7
+                        unit_write_drop_in_private(u, mode, name, b ? "TasksAccounting=yes" : "TasksAccounting=no");
803fb7
+                }
803fb7
+
803fb7
+                return 1;
803fb7
+
803fb7
+        } else if (streq(name, "TasksMax")) {
803fb7
+                uint64_t limit;
803fb7
+
803fb7
+                r = sd_bus_message_read(message, "t", &limit);
803fb7
+                if (r < 0)
803fb7
+                        return r;
803fb7
+
803fb7
+                if (mode != UNIT_CHECK) {
803fb7
+                        c->tasks_max = limit;
803fb7
+                        u->cgroup_realized_mask &= ~CGROUP_PIDS;
803fb7
+
803fb7
+                        if (limit == (uint64_t) -1)
803fb7
+                                unit_write_drop_in_private(u, mode, name, "TasksMax=infinity");
803fb7
+                        else
803fb7
+                                unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu64, limit);
803fb7
+                }
803fb7
+
803fb7
+                return 1;
803fb7
         }
803fb7
 
803fb7
         if (u->transient && u->load_state == UNIT_STUB) {
803fb7
diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c
803fb7
index 056a17ac1..1d0d6f67c 100644
803fb7
--- a/src/core/dbus-unit.c
803fb7
+++ b/src/core/dbus-unit.c
803fb7
@@ -673,11 +673,36 @@ static int property_get_current_memory(
803fb7
         return sd_bus_message_append(reply, "t", sz);
803fb7
 }
803fb7
 
803fb7
+static int property_get_current_tasks(
803fb7
+                sd_bus *bus,
803fb7
+                const char *path,
803fb7
+                const char *interface,
803fb7
+                const char *property,
803fb7
+                sd_bus_message *reply,
803fb7
+                void *userdata,
803fb7
+                sd_bus_error *error) {
803fb7
+
803fb7
+        uint64_t cn = (uint64_t) -1;
803fb7
+        Unit *u = userdata;
803fb7
+        int r;
803fb7
+
803fb7
+        assert(bus);
803fb7
+        assert(reply);
803fb7
+        assert(u);
803fb7
+
803fb7
+        r = unit_get_tasks_current(u, &cn;;
803fb7
+        if (r < 0 && r != -ENODATA)
803fb7
+                log_unit_warning_errno(u->id, r, "Failed to get pids.current attribute: %m");
803fb7
+
803fb7
+        return sd_bus_message_append(reply, "t", cn);
803fb7
+}
803fb7
+
803fb7
 const sd_bus_vtable bus_unit_cgroup_vtable[] = {
803fb7
         SD_BUS_VTABLE_START(0),
803fb7
         SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0),
803fb7
         SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Unit, cgroup_path), 0),
803fb7
         SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0),
803fb7
+        SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0),
803fb7
         SD_BUS_VTABLE_END
803fb7
 };
803fb7
 
803fb7
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
803fb7
index f996032cf..26e4c618e 100644
803fb7
--- a/src/core/load-fragment-gperf.gperf.m4
803fb7
+++ b/src/core/load-fragment-gperf.gperf.m4
803fb7
@@ -125,6 +125,8 @@ $1.StartupBlockIOWeight,         config_parse_blockio_weight,        0,
803fb7
 $1.BlockIODeviceWeight,          config_parse_blockio_device_weight, 0,                             offsetof($1, cgroup_context)
803fb7
 $1.BlockIOReadBandwidth,         config_parse_blockio_bandwidth,     0,                             offsetof($1, cgroup_context)
803fb7
 $1.BlockIOWriteBandwidth,        config_parse_blockio_bandwidth,     0,                             offsetof($1, cgroup_context)
803fb7
+$1.TasksAccounting,              config_parse_bool,                  0,                             offsetof($1, cgroup_context.tasks_accounting)
803fb7
+$1.TasksMax,                     config_parse_tasks_max,             0,                             offsetof($1, cgroup_context)
803fb7
 $1.Delegate,                     config_parse_bool,                  0,                             offsetof($1, cgroup_context.delegate)'
803fb7
 )m4_dnl
803fb7
 Unit.Description,                config_parse_unit_string_printf,    0,                             offsetof(Unit, description)
803fb7
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
803fb7
index 7d1ac6c25..7d2e737d0 100644
803fb7
--- a/src/core/load-fragment.c
803fb7
+++ b/src/core/load-fragment.c
803fb7
@@ -3052,7 +3052,7 @@ int config_parse_memory_limit(
803fb7
         off_t bytes;
803fb7
         int r;
803fb7
 
803fb7
-        if (isempty(rvalue)) {
803fb7
+        if (isempty(rvalue) || streq(rvalue, "infinity")) {
803fb7
                 c->memory_limit = (uint64_t) -1;
803fb7
                 return 0;
803fb7
         }
803fb7
@@ -3060,9 +3060,8 @@ int config_parse_memory_limit(
803fb7
         assert_cc(sizeof(uint64_t) == sizeof(off_t));
803fb7
 
803fb7
         r = parse_size(rvalue, 1024, &bytes);
803fb7
-        if (r < 0) {
803fb7
-                log_syntax(unit, LOG_ERR, filename, line, EINVAL,
803fb7
-                           "Memory limit '%s' invalid. Ignoring.", rvalue);
803fb7
+        if (r < 0 || bytes < 1) {
803fb7
+                log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Memory limit '%s' invalid. Ignoring.", rvalue);
803fb7
                 return 0;
803fb7
         }
803fb7
 
803fb7
@@ -3070,6 +3069,36 @@ int config_parse_memory_limit(
803fb7
         return 0;
803fb7
 }
803fb7
 
803fb7
+int config_parse_tasks_max(
803fb7
+                const char *unit,
803fb7
+                const char *filename,
803fb7
+                unsigned line,
803fb7
+                const char *section,
803fb7
+                unsigned section_line,
803fb7
+                const char *lvalue,
803fb7
+                int ltype,
803fb7
+                const char *rvalue,
803fb7
+                void *data,
803fb7
+                void *userdata) {
803fb7
+
803fb7
+        CGroupContext *c = data;
803fb7
+        uint64_t u;
803fb7
+        int r;
803fb7
+
803fb7
+        if (isempty(rvalue) || streq(rvalue, "infinity")) {
803fb7
+                c->tasks_max = (uint64_t) -1;
803fb7
+                return 0;
803fb7
+        }
803fb7
+
803fb7
+        r = safe_atou64(rvalue, &u);
803fb7
+        if (r < 0 || u < 1) {
803fb7
+                log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Maximum tasks value '%s' invalid. Ignoring.", rvalue);
803fb7
+                return 0;
803fb7
+        }
803fb7
+
803fb7
+        return 0;
803fb7
+}
803fb7
+
803fb7
 int config_parse_device_allow(
803fb7
                 const char *unit,
803fb7
                 const char *filename,
803fb7
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
803fb7
index 2059353d3..8d334f2c8 100644
803fb7
--- a/src/core/load-fragment.h
803fb7
+++ b/src/core/load-fragment.h
803fb7
@@ -89,6 +89,7 @@ int config_parse_pass_environ(const char *unit, const char *filename, unsigned l
803fb7
 int config_parse_unit_slice(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
803fb7
 int config_parse_cpu_shares(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
803fb7
 int config_parse_memory_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
803fb7
+int config_parse_tasks_max(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
803fb7
 int config_parse_device_policy(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
803fb7
 int config_parse_device_allow(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
803fb7
 int config_parse_blockio_weight(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
803fb7
diff --git a/src/core/main.c b/src/core/main.c
803fb7
index cba992cea..aca05a535 100644
803fb7
--- a/src/core/main.c
803fb7
+++ b/src/core/main.c
803fb7
@@ -117,6 +117,7 @@ static bool arg_default_cpu_accounting = false;
803fb7
 static bool arg_default_blockio_accounting = false;
803fb7
 static bool arg_default_memory_accounting = false;
803fb7
 static EmergencyAction arg_cad_burst_action = EMERGENCY_ACTION_REBOOT_FORCE;
803fb7
+static bool arg_default_tasks_accounting = false;
803fb7
 
803fb7
 static void nop_handler(int sig) {}
803fb7
 
803fb7
@@ -676,6 +677,7 @@ static int parse_config_file(void) {
803fb7
                 { "Manager", "DefaultBlockIOAccounting",  config_parse_bool,             0, &arg_default_blockio_accounting        },
803fb7
                 { "Manager", "DefaultMemoryAccounting",   config_parse_bool,             0, &arg_default_memory_accounting         },
803fb7
                 { "Manager", "CtrlAltDelBurstAction",     config_parse_emergency_action, 0, &arg_cad_burst_action                  },
803fb7
+                { "Manager", "DefaultTasksAccounting",    config_parse_bool,             0, &arg_default_tasks_accounting          },
803fb7
                 {}
803fb7
         };
803fb7
 
803fb7
@@ -1685,6 +1687,7 @@ int main(int argc, char *argv[]) {
803fb7
         m->default_cpu_accounting = arg_default_cpu_accounting;
803fb7
         m->default_blockio_accounting = arg_default_blockio_accounting;
803fb7
         m->default_memory_accounting = arg_default_memory_accounting;
803fb7
+        m->default_tasks_accounting = arg_default_tasks_accounting;
803fb7
         m->runtime_watchdog = arg_runtime_watchdog;
803fb7
         m->shutdown_watchdog = arg_shutdown_watchdog;
803fb7
 
803fb7
diff --git a/src/core/manager.h b/src/core/manager.h
803fb7
index 231c076b1..96dcd83dc 100644
803fb7
--- a/src/core/manager.h
803fb7
+++ b/src/core/manager.h
803fb7
@@ -261,6 +261,7 @@ struct Manager {
803fb7
         bool default_cpu_accounting;
803fb7
         bool default_memory_accounting;
803fb7
         bool default_blockio_accounting;
803fb7
+        bool default_tasks_accounting;
803fb7
 
803fb7
         usec_t default_timer_accuracy_usec;
803fb7
 
803fb7
diff --git a/src/core/unit.c b/src/core/unit.c
803fb7
index 103f92084..2fcb4fbf0 100644
803fb7
--- a/src/core/unit.c
803fb7
+++ b/src/core/unit.c
803fb7
@@ -126,6 +126,7 @@ static void unit_init(Unit *u) {
803fb7
                 cc->cpu_accounting = u->manager->default_cpu_accounting;
803fb7
                 cc->blockio_accounting = u->manager->default_blockio_accounting;
803fb7
                 cc->memory_accounting = u->manager->default_memory_accounting;
803fb7
+                cc->tasks_accounting = u->manager->default_tasks_accounting;
803fb7
         }
803fb7
 
803fb7
         ec = unit_get_exec_context(u);
803fb7
diff --git a/src/shared/cgroup-util.h b/src/shared/cgroup-util.h
803fb7
index 96a3d3baf..31bd8d311 100644
803fb7
--- a/src/shared/cgroup-util.h
803fb7
+++ b/src/shared/cgroup-util.h
803fb7
@@ -35,6 +35,7 @@ typedef enum CGroupControllerMask {
803fb7
         CGROUP_BLKIO = 4,
803fb7
         CGROUP_MEMORY = 8,
803fb7
         CGROUP_DEVICE = 16,
803fb7
+        CGROUP_PIDS = 32,
803fb7
         _CGROUP_CONTROLLER_MASK_ALL = 31
803fb7
 } CGroupControllerMask;
803fb7
 
803fb7
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
803fb7
index 0333599c8..b1862b567 100644
803fb7
--- a/src/systemctl/systemctl.c
803fb7
+++ b/src/systemctl/systemctl.c
803fb7
@@ -3280,6 +3280,8 @@ typedef struct UnitStatusInfo {
803fb7
         /* CGroup */
803fb7
         uint64_t memory_current;
803fb7
         uint64_t memory_limit;
803fb7
+        uint64_t tasks_current;
803fb7
+        uint64_t tasks_max;
803fb7
 
803fb7
         LIST_HEAD(ExecStatusInfo, exec);
803fb7
 } UnitStatusInfo;
803fb7
@@ -3539,6 +3541,15 @@ static void print_status_info(
803fb7
         if (i->status_errno > 0)
803fb7
                 printf("    Error: %i (%s)\n", i->status_errno, strerror(i->status_errno));
803fb7
 
803fb7
+        if (i->tasks_current != (uint64_t) -1) {
803fb7
+                printf("    Tasks: %" PRIu64, i->tasks_current);
803fb7
+
803fb7
+                if (i->tasks_max != (uint64_t) -1)
803fb7
+                        printf(" (limit: %" PRIi64 ")\n", i->tasks_max);
803fb7
+                else
803fb7
+                        printf("\n");
803fb7
+        }
803fb7
+
803fb7
         if (i->memory_current != (uint64_t) -1) {
803fb7
                 char buf[FORMAT_BYTES_MAX];
803fb7
 
803fb7
@@ -3768,6 +3779,10 @@ static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *
803fb7
                         i->memory_current = u;
803fb7
                 else if (streq(name, "MemoryLimit"))
803fb7
                         i->memory_limit = u;
803fb7
+                else if (streq(name, "TasksCurrent"))
803fb7
+                        i->tasks_current = u;
803fb7
+                else if (streq(name, "TasksMax"))
803fb7
+                        i->tasks_max = u;
803fb7
 
803fb7
                 break;
803fb7
         }
803fb7
@@ -4248,6 +4263,8 @@ static int show_one(
803fb7
         UnitStatusInfo info = {
803fb7
                 .memory_current = (uint64_t) -1,
803fb7
                 .memory_limit = (uint64_t) -1,
803fb7
+                .tasks_current = (uint64_t) -1,
803fb7
+                .tasks_max = (uint64_t) -1,
803fb7
         };
803fb7
         ExecStatusInfo *p;
803fb7
         int r;