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.
@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 = ['...', ...];
<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"/>
<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
<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>
<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>
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;
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),
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) {
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;
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;
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))),
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"),