From: Mike Yuan Date: Sat, 4 Oct 2025 23:46:40 +0000 (+0200) Subject: core: expose transactions with ordering cycle X-Git-Tag: v259-rc1~68^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=58686034ebcf089f4c3e0abed37fe6df3a831692;p=thirdparty%2Fsystemd.git core: expose transactions with ordering cycle 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. --- diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index f1f433bbac9..8d16d6618d3 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -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 { + + @@ -1809,6 +1813,9 @@ node /org/freedesktop/systemd1 { NFailedJobs encodes how many jobs have ever failed in total. + TransactionsWithOrderingCycle encodes IDs of transactions that encountered + ordering cycle. + Progress 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 \ DefaultRestrictSUIDSGID, RemoveSubgroupFromUnit(), and KillUnitSubgroup() were added in version 258. + TransactionsWithOrderingCycle was added in version 259. Unit Objects diff --git a/man/systemctl.xml b/man/systemctl.xml index 8fb17e284d6..a290e990e5e 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -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 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 degraded state - returned by is-system-running. Returns an exit code 0 + check whether there are any failed units or ordering cycles, which corresponds to the degraded + state returned by is-system-running. Returns an exit code 0 if at least one has failed, non-zero otherwise. Unless is specified, this will also print the current unit or system state to standard output. diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 26104423844..ac203f2971e 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -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), diff --git a/src/core/manager.c b/src/core/manager.c index 0a6ea1658f1..acf837941eb 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -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; diff --git a/src/core/varlink-manager.c b/src/core/varlink-manager.c index 3414ccb9edc..951e0b4b96a 100644 --- a/src/core/varlink-manager.c +++ b/src/core/varlink-manager.c @@ -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))), diff --git a/src/shared/varlink-io.systemd.Manager.c b/src/shared/varlink-io.systemd.Manager.c index cdcaa53b716..a31e0e688b0 100644 --- a/src/shared/varlink-io.systemd.Manager.c +++ b/src/shared/varlink-io.systemd.Manager.c @@ -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"),