]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: expose transactions with ordering cycle
authorMike Yuan <me@yhndnzj.com>
Sat, 4 Oct 2025 23:46:40 +0000 (01:46 +0200)
committerMike Yuan <me@yhndnzj.com>
Wed, 12 Nov 2025 22:47:39 +0000 (23:47 +0100)
Closes #3829
Alternative to #35417

I don't think the individual "WasOnDependencyCycle" attrs on units
are particularly helpful and comprehensible, as it's really about
the dep relationship between them. And as discussed, the dependency
cycle is not something persistent, rather local to the currently
loaded set of units and shall be reset with daemon-reload (see also
https://github.com/systemd/systemd/issues/35642#issuecomment-2591296586).

Hence, let's report system state as degraded and point users to
the involved transactions when ordering cycles are encountered instead.
Combined with log messages added in 6912eb315fabe0bbf25593ab897265fa79a7e24b
it should achieve the goal of making ordering cycles more observable,
while avoiding all sorts of subtle bookkeeping in the service manager.
The degraded state can be reset via the existing ResetFailed() manager-wide
method.

man/org.freedesktop.systemd1.xml
man/systemctl.xml
src/core/dbus-manager.c
src/core/manager.c
src/core/varlink-manager.c
src/shared/varlink-io.systemd.Manager.c

index f1f433bbac9c4931a457c8d9c00c188a88da4ea7..8d16d6618d32af505bac4ac5f09a4908b94f98be 100644 (file)
@@ -413,6 +413,8 @@ node /org/freedesktop/systemd1 {
       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
       readonly u NFailedJobs = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
+      readonly at TransactionsWithOrderingCycle = [...];
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
       readonly d Progress = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
       readonly as Environment = ['...', ...];
@@ -1103,6 +1105,8 @@ node /org/freedesktop/systemd1 {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NFailedJobs"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="TransactionsWithOrderingCycle"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="Progress"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="Environment"/>
@@ -1809,6 +1813,9 @@ node /org/freedesktop/systemd1 {
 
       <para><varname>NFailedJobs</varname> encodes how many jobs have ever failed in total.</para>
 
+      <para><varname>TransactionsWithOrderingCycle</varname> encodes IDs of transactions that encountered
+      ordering cycle.</para>
+
       <para><varname>Progress</varname> encodes boot progress as a floating point value between 0.0 and
       1.0. This value begins at 0.0 at early-boot and ends at 1.0 when boot is finished and is based on the
       number of executed and queued jobs. After startup, this field is always 1.0 indicating a finished
@@ -12465,6 +12472,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <para><varname>DefaultRestrictSUIDSGID</varname>,
       <function>RemoveSubgroupFromUnit()</function>, and
       <function>KillUnitSubgroup()</function> were added in version 258.</para>
+      <para><varname>TransactionsWithOrderingCycle</varname> was added in version 259.</para>
     </refsect2>
     <refsect2>
       <title>Unit Objects</title>
index 8fb17e284d62430010eb97a2b955f1245ea389f2..a290e990e5e91e9aa9388754b38736794b98646c 100644 (file)
@@ -225,8 +225,8 @@ Sun 2017-02-26 20:57:49 EST  2h 3min left  Sun 2017-02-26 11:56:36 EST  6h ago
 
           <listitem>
             <para>Check whether any of the specified units is in the "failed" state. If no unit is specified,
-            check whether there are any failed units, which corresponds to the <literal>degraded</literal> state
-            returned by <command>is-system-running</command>. Returns an exit code <constant>0</constant>
+            check whether there are any failed units or ordering cycles, which corresponds to the <literal>degraded</literal>
+            state returned by <command>is-system-running</command>. Returns an exit code <constant>0</constant>
             if at least one has failed, non-zero otherwise. Unless <option>--quiet</option> is specified, this
             will also print the current unit or system state to standard output.</para>
 
index 2610442384475b7bf65de0e623dc64b80c3b56ac..ac203f2971e917ea6ee6eb19bf914bbc4c20c17a 100644 (file)
@@ -455,6 +455,35 @@ static int property_get_oom_score_adjust(
         return sd_bus_message_append(reply, "i", n);
 }
 
+static int property_get_transactions_with_cycle(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Manager *m = ASSERT_PTR(userdata);
+        int r;
+
+        assert(bus);
+        assert(reply);
+
+        r = sd_bus_message_open_container(reply, 'a', "t");
+        if (r < 0)
+                return r;
+
+        uint64_t *id;
+        SET_FOREACH(id, m->transactions_with_cycle) {
+                r = sd_bus_message_append_basic(reply, 't', id);
+                if (r < 0)
+                        return r;
+        }
+
+        return sd_bus_message_close_container(reply);
+}
+
 static int bus_get_unit_by_name(Manager *m, sd_bus_message *message, const char *name, Unit **ret_unit, sd_bus_error *error) {
         Unit *u;
         int r;
@@ -2870,6 +2899,7 @@ const sd_bus_vtable bus_manager_vtable[] = {
         SD_BUS_PROPERTY("NJobs", "u", property_get_hashmap_size, offsetof(Manager, jobs), 0),
         SD_BUS_PROPERTY("NInstalledJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_installed_jobs), 0),
         SD_BUS_PROPERTY("NFailedJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_failed_jobs), 0),
+        SD_BUS_PROPERTY("TransactionsWithOrderingCycle", "at", property_get_transactions_with_cycle, 0, 0),
         SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0),
         SD_BUS_PROPERTY("Environment", "as", property_get_environment, 0, 0),
         SD_BUS_PROPERTY("ConfirmSpawn", "b", bus_property_get_bool, offsetof(Manager, confirm_spawn), SD_BUS_VTABLE_PROPERTY_CONST),
index 0a6ea1658f15b4dc4764132e6491257ab7074f6d..acf837941eb054533ac8c61985cb5dba2fd09a06 100644 (file)
@@ -3675,6 +3675,8 @@ void manager_reset_failed(Manager *m) {
 
         HASHMAP_FOREACH(u, m->units)
                 unit_reset_failed(u);
+
+        m->transactions_with_cycle = set_free(m->transactions_with_cycle);
 }
 
 bool manager_unit_inactive_or_pending(Manager *m, const char *name) {
@@ -4623,8 +4625,8 @@ ManagerState manager_state(Manager *m) {
                         return MANAGER_MAINTENANCE;
         }
 
-        /* Are there any failed units? If so, we are in degraded mode */
-        if (!set_isempty(m->failed_units))
+        /* Are there any failed units or ordering cycles? If so, we are in degraded mode */
+        if (!set_isempty(m->failed_units) || !set_isempty(m->transactions_with_cycle))
                 return MANAGER_DEGRADED;
 
         return MANAGER_RUNNING;
index 3414ccb9edc6f2e792efdb629e09894f9327b762..951e0b4b96ac0fd08e5e12aa32986f603219733f 100644 (file)
@@ -112,6 +112,24 @@ static int manager_context_build_json(sd_json_variant **ret, const char *name, v
                         JSON_BUILD_PAIR_STRING_NON_EMPTY("ControlGroup", m->cgroup_root));
 }
 
+static int transactions_with_cycle_build_json(sd_json_variant **ret, const char *name, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        const Set *ids = userdata;
+        int r;
+
+        assert(ret);
+
+        uint64_t *id;
+        SET_FOREACH(id, ids) {
+                r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_UNSIGNED(*id));
+                if (r < 0)
+                        return r;
+        }
+
+        *ret = TAKE_PTR(v);
+        return 0;
+}
+
 static int manager_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) {
         Manager *m = ASSERT_PTR(userdata);
         dual_timestamp watchdog_last_ping;
@@ -154,6 +172,7 @@ static int manager_runtime_build_json(sd_json_variant **ret, const char *name, v
                 SD_JSON_BUILD_PAIR_UNSIGNED("NJobs", hashmap_size(m->jobs)),
                 SD_JSON_BUILD_PAIR_UNSIGNED("NInstalledJobs", m->n_installed_jobs),
                 SD_JSON_BUILD_PAIR_UNSIGNED("NFailedJobs", m->n_failed_jobs),
+                JSON_BUILD_PAIR_CALLBACK_NON_NULL("TransactionsWithOrderingCycle", transactions_with_cycle_build_json, m->transactions_with_cycle),
                 SD_JSON_BUILD_PAIR_REAL("Progress", manager_get_progress(m)),
                 JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("WatchdogLastPingTimestamp", watchdog_get_last_ping_as_dual_timestamp(&watchdog_last_ping)),
                 SD_JSON_BUILD_PAIR_STRING("SystemState", manager_state_to_string(manager_state(m))),
index cdcaa53b71682d8322f0b95b95df232a05da850b..a31e0e688b0a0ed4377d32b1e899e10b5c7b4d02 100644 (file)
@@ -155,6 +155,8 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
                 SD_VARLINK_DEFINE_FIELD(NInstalledJobs, SD_VARLINK_INT, 0),
                 SD_VARLINK_FIELD_COMMENT("The total amount of failed jobs"),
                 SD_VARLINK_DEFINE_FIELD(NFailedJobs, SD_VARLINK_INT, 0),
+                SD_VARLINK_FIELD_COMMENT("IDs of transactions that encountered ordering cycle"),
+                SD_VARLINK_DEFINE_FIELD(TransactionsWithOrderingCycle, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Boot progress as a floating point value between 0.0 and 1.0"),
                 SD_VARLINK_DEFINE_FIELD(Progress, SD_VARLINK_FLOAT, 0),
                 SD_VARLINK_FIELD_COMMENT("Timestamp when the hardware watchdog was last pinged"),