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