984f77
From 9d3f5e5d222308d29aad9bf7b2bfc440143a8606 Mon Sep 17 00:00:00 2001
984f77
From: Daan De Meyer <daan.j.demeyer@gmail.com>
984f77
Date: Fri, 17 Dec 2021 20:01:31 +0100
984f77
Subject: [PATCH] core: Add trigger limit for path units
984f77
984f77
When conditions fail on a service unit, a path unit can cause
984f77
PID 1 to busy loop as it keeps trying to activate the service unit.
984f77
To avoid this from happening, add a trigger limit to the path unit,
984f77
identical to the trigger limit we have for socket units.
984f77
984f77
Initially, let's start with a high limit and not make it configurable.
984f77
If needed, we can add properties to configure the rate limit similar
984f77
to the ones we have for socket units.
984f77
984f77
(cherry picked from commit aaae822b37aa3ca39aebb516fdc6bef36d730c25)
984f77
984f77
Resolves: #2114005
984f77
---
984f77
 src/core/path.c                            | 10 ++++++++++
984f77
 src/core/path.h                            |  3 +++
984f77
 test/TEST-63-ISSUE-17433/test63.service    |  2 +-
984f77
 test/TEST-63-ISSUE-17433/testsuite.service | 21 +++++++++++++++++----
984f77
 4 files changed, 31 insertions(+), 5 deletions(-)
984f77
984f77
diff --git a/src/core/path.c b/src/core/path.c
984f77
index c2facf0b16..b899bde0de 100644
984f77
--- a/src/core/path.c
984f77
+++ b/src/core/path.c
984f77
@@ -238,6 +238,9 @@ static void path_init(Unit *u) {
984f77
         assert(u->load_state == UNIT_STUB);
984f77
 
984f77
         p->directory_mode = 0755;
984f77
+
984f77
+        p->trigger_limit.interval = 2 * USEC_PER_SEC;
984f77
+        p->trigger_limit.burst = 200;
984f77
 }
984f77
 
984f77
 void path_free_specs(Path *p) {
984f77
@@ -467,6 +470,12 @@ static void path_enter_running(Path *p) {
984f77
         if (unit_stop_pending(UNIT(p)))
984f77
                 return;
984f77
 
984f77
+        if (!ratelimit_below(&p->trigger_limit)) {
984f77
+                log_unit_warning(UNIT(p), "Trigger limit hit, refusing further activation.");
984f77
+                path_enter_dead(p, PATH_FAILURE_TRIGGER_LIMIT_HIT);
984f77
+                return;
984f77
+        }
984f77
+
984f77
         trigger = UNIT_TRIGGER(UNIT(p));
984f77
         if (!trigger) {
984f77
                 log_unit_error(UNIT(p), "Unit to trigger vanished.");
984f77
@@ -767,6 +776,7 @@ static const char* const path_result_table[_PATH_RESULT_MAX] = {
984f77
         [PATH_FAILURE_RESOURCES] = "resources",
984f77
         [PATH_FAILURE_START_LIMIT_HIT] = "start-limit-hit",
984f77
         [PATH_FAILURE_UNIT_START_LIMIT_HIT] = "unit-start-limit-hit",
984f77
+        [PATH_FAILURE_TRIGGER_LIMIT_HIT]    = "trigger-limit-hit",
984f77
 };
984f77
 
984f77
 DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult);
984f77
diff --git a/src/core/path.h b/src/core/path.h
984f77
index 8a69f06c13..12fd13fbe3 100644
984f77
--- a/src/core/path.h
984f77
+++ b/src/core/path.h
984f77
@@ -46,6 +46,7 @@ typedef enum PathResult {
984f77
         PATH_FAILURE_RESOURCES,
984f77
         PATH_FAILURE_START_LIMIT_HIT,
984f77
         PATH_FAILURE_UNIT_START_LIMIT_HIT,
984f77
+        PATH_FAILURE_TRIGGER_LIMIT_HIT,
984f77
         _PATH_RESULT_MAX,
984f77
         _PATH_RESULT_INVALID = -1
984f77
 } PathResult;
984f77
@@ -63,6 +64,8 @@ struct Path {
984f77
         mode_t directory_mode;
984f77
 
984f77
         PathResult result;
984f77
+
984f77
+        RateLimit trigger_limit;
984f77
 };
984f77
 
984f77
 void path_free_specs(Path *p);
984f77
diff --git a/test/TEST-63-ISSUE-17433/test63.service b/test/TEST-63-ISSUE-17433/test63.service
984f77
index c83801874d..6292434c5c 100644
984f77
--- a/test/TEST-63-ISSUE-17433/test63.service
984f77
+++ b/test/TEST-63-ISSUE-17433/test63.service
984f77
@@ -1,5 +1,5 @@
984f77
 [Unit]
984f77
-ConditionPathExists=!/tmp/nonexistent
984f77
+ConditionPathExists=/tmp/nonexistent
984f77
 
984f77
 [Service]
984f77
 ExecStart=true
984f77
diff --git a/test/TEST-63-ISSUE-17433/testsuite.service b/test/TEST-63-ISSUE-17433/testsuite.service
984f77
index d3ca5b002b..39f9643890 100644
984f77
--- a/test/TEST-63-ISSUE-17433/testsuite.service
984f77
+++ b/test/TEST-63-ISSUE-17433/testsuite.service
984f77
@@ -4,14 +4,27 @@ Description=TEST-63-ISSUE-17433
984f77
 [Service]
984f77
 ExecStartPre=rm -f /failed /testok
984f77
 Type=oneshot
984f77
+
984f77
+# Test that a path unit continuously triggering a service that fails condition checks eventually fails with
984f77
+# the trigger-limit-hit error.
984f77
 ExecStart=rm -f /tmp/nonexistent
984f77
 ExecStart=systemctl start test63.path
984f77
 ExecStart=touch /tmp/test63
984f77
-# Make sure systemd has sufficient time to hit the start limit for test63.service.
984f77
+# Make sure systemd has sufficient time to hit the trigger limit for test63.path.
984f77
 ExecStart=sleep 2
984f77
-ExecStart=sh -x -c 'test "$(systemctl show test63.service --value -p ActiveState)" = failed'
984f77
-ExecStart=sh -x -c 'test "$(systemctl show test63.service --value -p Result)" = start-limit-hit'
984f77
+ExecStart=sh -x -c 'test "$(systemctl show test63.service --value -p ActiveState)" = inactive'
984f77
+ExecStart=sh -x -c 'test "$(systemctl show test63.service --value -p Result)" = success'
984f77
 # FIXME: The path remains active, which it should not
984f77
 # ExecStart=sh -x -c 'test "$(systemctl show test63.path --value -p ActiveState)" = failed'
984f77
-# ExecStart=sh -x -c 'test "$(systemctl show test63.path --value -p Result)" = unit-start-limit-hit'
984f77
+# ExecStart=sh -x -c 'test "$(systemctl show test63.path --value -p Result)" = trigger-limit-hit'
984f77
+
984f77
+# Test that starting the service manually doesn't affect the path unit.
984f77
+ExecStart=rm -f /tmp/test63
984f77
+ExecStart=systemctl reset-failed
984f77
+ExecStart=systemctl start test63.path
984f77
+ExecStart=systemctl start test63.service
984f77
+ExecStart=sh -x -c 'test "$(systemctl show test63.service --value -p ActiveState)" = inactive'
984f77
+ExecStart=sh -x -c 'test "$(systemctl show test63.service --value -p Result)" = success'
984f77
+ExecStart=sh -x -c 'test "$(systemctl show test63.path --value -p ActiveState)" = active'
984f77
+ExecStart=sh -x -c 'test "$(systemctl show test63.path --value -p Result)" = success'
984f77
 ExecStart=sh -x -c 'echo OK >/testok'