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