ff2b41
From 9684908410868cb590ad74f4747bdbf9b5790abb Mon Sep 17 00:00:00 2001
ff2b41
From: Lennart Poettering <lennart@poettering.net>
ff2b41
Date: Mon, 13 Nov 2017 15:08:49 +0100
ff2b41
Subject: [PATCH] unit: rework a bit how we keep the service fdstore from being
ff2b41
 destroyed during service restart
ff2b41
ff2b41
When preparing for a restart we quickly go through the DEAD/INACTIVE
ff2b41
service state before entering AUTO_RESTART. When doing this, we need to
ff2b41
make sure we don't destroy the FD store. Previously this was done by
ff2b41
checking the failure state of the unit, and keeping the FD store around
ff2b41
when the unit failed, under the assumption that the restart logic will
ff2b41
then get into action.
ff2b41
ff2b41
This is not entirely correct howver, as there might be failure states
ff2b41
that will no result in restarts.
ff2b41
ff2b41
With this commit we slightly alter the logic: a ref counter for the fd
ff2b41
store is added, that is increased right before we handle the restart
ff2b41
logic, and decreased again right-after.
ff2b41
ff2b41
This should ensure that the fdstore lives exactly as long as it needs.
ff2b41
ff2b41
Follow-up for f0bfbfac43b7faa68ef1bb2ad659c191b9ec85d2.
ff2b41
ff2b41
(cherry picked from commit 7eb2a8a1259043e107ebec94e30ed160a93f40a7)
ff2b41
(cherry picked from commit e2636bde0f07319d0d35262dac6ff2638ba4e598)
ff2b41
Resolves: #1803802
ff2b41
---
ff2b41
 src/core/service.c | 23 ++++++++++++++++++-----
ff2b41
 src/core/service.h |  1 +
ff2b41
 src/core/unit.c    | 12 ++++--------
ff2b41
 src/core/unit.h    |  5 ++---
ff2b41
 4 files changed, 25 insertions(+), 16 deletions(-)
ff2b41
ff2b41
diff --git a/src/core/service.c b/src/core/service.c
ff2b41
index 3c2f69a003..e32cdf4594 100644
ff2b41
--- a/src/core/service.c
ff2b41
+++ b/src/core/service.c
ff2b41
@@ -261,6 +261,9 @@ static void service_fd_store_unlink(ServiceFDStore *fs) {
ff2b41
 static void service_release_fd_store(Service *s) {
ff2b41
         assert(s);
ff2b41
 
ff2b41
+        if (s->n_keep_fd_store > 0)
ff2b41
+                return;
ff2b41
+
ff2b41
         log_unit_debug(UNIT(s)->id, "Releasing all stored fds");
ff2b41
         while (s->fd_store)
ff2b41
                 service_fd_store_unlink(s->fd_store);
ff2b41
@@ -268,7 +271,7 @@ static void service_release_fd_store(Service *s) {
ff2b41
         assert(s->n_fd_store == 0);
ff2b41
 }
ff2b41
 
ff2b41
-static void service_release_resources(Unit *u, bool inactive) {
ff2b41
+static void service_release_resources(Unit *u) {
ff2b41
         Service *s = SERVICE(u);
ff2b41
 
ff2b41
         assert(s);
ff2b41
@@ -278,8 +281,7 @@ static void service_release_resources(Unit *u, bool inactive) {
ff2b41
 
ff2b41
         log_unit_debug(u->id, "Releasing resources.");
ff2b41
 
ff2b41
-        if (inactive)
ff2b41
-                service_release_fd_store(s);
ff2b41
+        service_release_fd_store(s);
ff2b41
 }
ff2b41
 
ff2b41
 static void service_done(Unit *u) {
ff2b41
@@ -327,7 +329,7 @@ static void service_done(Unit *u) {
ff2b41
 
ff2b41
         s->timer_event_source = sd_event_source_unref(s->timer_event_source);
ff2b41
 
ff2b41
-        service_release_resources(u, true);
ff2b41
+        service_release_resources(u);
ff2b41
 }
ff2b41
 
ff2b41
 static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) {
ff2b41
@@ -1330,6 +1332,10 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
ff2b41
         if (f != SERVICE_SUCCESS)
ff2b41
                 s->result = f;
ff2b41
 
ff2b41
+        /* Make sure service_release_resources() doesn't destroy our FD store, while we are changing through
ff2b41
+         * SERVICE_FAILED/SERVICE_DEAD before entering into SERVICE_AUTO_RESTART. */
ff2b41
+        s->n_keep_fd_store++;
ff2b41
+
ff2b41
         service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD);
ff2b41
 
ff2b41
         if (s->result != SERVICE_SUCCESS) {
ff2b41
@@ -1351,12 +1357,19 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
ff2b41
             (!IN_SET(s->main_exec_status.code, CLD_KILLED, CLD_DUMPED) || !set_contains(s->restart_prevent_status.signal, INT_TO_PTR(s->main_exec_status.status)))) {
ff2b41
 
ff2b41
                 r = service_arm_timer(s, s->restart_usec);
ff2b41
-                if (r < 0)
ff2b41
+                if (r < 0) {
ff2b41
+                        s->n_keep_fd_store--;
ff2b41
                         goto fail;
ff2b41
+                }
ff2b41
 
ff2b41
                 service_set_state(s, SERVICE_AUTO_RESTART);
ff2b41
         }
ff2b41
 
ff2b41
+        /* The new state is in effect, let's decrease the fd store ref counter again. Let's also readd us to the GC
ff2b41
+         * queue, so that the fd store is possibly gc'ed again */
ff2b41
+        s->n_keep_fd_store--;
ff2b41
+        unit_add_to_gc_queue(UNIT(s));
ff2b41
+
ff2b41
         s->forbid_restart = false;
ff2b41
 
ff2b41
         /* We want fresh tmpdirs in case service is started again immediately */
ff2b41
diff --git a/src/core/service.h b/src/core/service.h
ff2b41
index 82938a1fc4..e5753f1f4c 100644
ff2b41
--- a/src/core/service.h
ff2b41
+++ b/src/core/service.h
ff2b41
@@ -215,6 +215,7 @@ struct Service {
ff2b41
         ServiceFDStore *fd_store;
ff2b41
         unsigned n_fd_store;
ff2b41
         unsigned n_fd_store_max;
ff2b41
+        unsigned n_keep_fd_store;
ff2b41
 };
ff2b41
 
ff2b41
 extern const UnitVTable service_vtable;
ff2b41
diff --git a/src/core/unit.c b/src/core/unit.c
ff2b41
index 22e31a76b3..eff9fdbe70 100644
ff2b41
--- a/src/core/unit.c
ff2b41
+++ b/src/core/unit.c
ff2b41
@@ -285,7 +285,6 @@ int unit_set_description(Unit *u, const char *description) {
ff2b41
 
ff2b41
 bool unit_may_gc(Unit *u) {
ff2b41
         UnitActiveState state;
ff2b41
-        bool inactive;
ff2b41
         assert(u);
ff2b41
 
ff2b41
         /* Checks whether the unit is ready to be unloaded for garbage collection.
ff2b41
@@ -303,17 +302,14 @@ bool unit_may_gc(Unit *u) {
ff2b41
                 return false;
ff2b41
 
ff2b41
         state = unit_active_state(u);
ff2b41
-        inactive = state == UNIT_INACTIVE;
ff2b41
 
ff2b41
-        /* If the unit is inactive and failed and no job is queued for
ff2b41
-         * it, then release its runtime resources */
ff2b41
+        /* If the unit is inactive and failed and no job is queued for it, then release its runtime resources */
ff2b41
         if (UNIT_IS_INACTIVE_OR_FAILED(state) &&
ff2b41
             UNIT_VTABLE(u)->release_resources)
ff2b41
-                UNIT_VTABLE(u)->release_resources(u, inactive);
ff2b41
+                UNIT_VTABLE(u)->release_resources(u);
ff2b41
 
ff2b41
-        /* But we keep the unit object around for longer when it is
ff2b41
-         * referenced or configured to not be gc'ed */
ff2b41
-        if (!inactive)
ff2b41
+        /* But we keep the unit object around for longer when it is referenced or configured to not be gc'ed */
ff2b41
+        if (state != UNIT_INACTIVE)
ff2b41
                 return false;
ff2b41
 
ff2b41
         if (UNIT_VTABLE(u)->no_gc)
ff2b41
diff --git a/src/core/unit.h b/src/core/unit.h
ff2b41
index 232be8164f..3b0fd8d9df 100644
ff2b41
--- a/src/core/unit.h
ff2b41
+++ b/src/core/unit.h
ff2b41
@@ -358,9 +358,8 @@ struct UnitVTable {
ff2b41
          * even though nothing references it and it isn't active in any way. */
ff2b41
         bool (*may_gc)(Unit *u);
ff2b41
 
ff2b41
-        /* When the unit is not running and no job for it queued we
ff2b41
-         * shall release its runtime resources */
ff2b41
-        void (*release_resources)(Unit *u, bool inactive);
ff2b41
+        /* When the unit is not running and no job for it queued we shall release its runtime resources */
ff2b41
+        void (*release_resources)(Unit *u);
ff2b41
 
ff2b41
         /* Return true when this unit is suitable for snapshotting */
ff2b41
         bool (*check_snapshot)(Unit *u);