1
0
mirror of https://github.com/systemd/systemd synced 2026-03-14 09:04:47 +01:00

Compare commits

..

No commits in common. "8b6d8ec66fd65e2a4bd92257e6545b696e415f8c" and "e047394797c43f0c666237b4498f5be33d006383" have entirely different histories.

10 changed files with 204 additions and 248 deletions

View File

@ -25,7 +25,7 @@ jobs:
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: systemd/mkosi@14d2d37a1923c03062f55454b2b61d0c64db6238 - uses: systemd/mkosi@e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14
# Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space
# immediately, we remove the files in the background. However, we first move them to a different location # immediately, we remove the files in the background. However, we first move them to a different location

View File

@ -39,7 +39,7 @@ jobs:
GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml GITHUB_ACTIONS_CONFIG_FILE: actionlint.yml
ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false ENABLE_GITHUB_PULL_REQUEST_SUMMARY_COMMENT: false
- uses: systemd/mkosi@14d2d37a1923c03062f55454b2b61d0c64db6238 - uses: systemd/mkosi@e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14
- name: Check that tabs are not used in Python code - name: Check that tabs are not used in Python code
run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py' run: sh -c '! git grep -P "\\t" -- src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py'

View File

@ -167,7 +167,7 @@ jobs:
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: systemd/mkosi@14d2d37a1923c03062f55454b2b61d0c64db6238 - uses: systemd/mkosi@e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14
# Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space
# immediately, we remove the files in the background. However, we first move them to a different location # immediately, we remove the files in the background. However, we first move them to a different location

View File

@ -96,13 +96,12 @@
<para>If the system has no battery, it would be hibernated after <varname>HibernateDelaySec=</varname> <para>If the system has no battery, it would be hibernated after <varname>HibernateDelaySec=</varname>
has passed. If not set, then defaults to <literal>2h</literal>.</para> has passed. If not set, then defaults to <literal>2h</literal>.</para>
<para>If the system has battery, low-battery alarms (ACPI _BTP) are tried first for detecting <para>If the system has battery and <varname>HibernateDelaySec=</varname> is not set, low-battery
battery percentage and wake up the system for hibernation. If <varname>HibernateDelaySec=</varname> alarms (ACPI _BTP) are tried first for detecting battery percentage and wake up the system for hibernation.
is also set, an additional timer is configured so that the system hibernates on whichever comes If not available, or <varname>HibernateDelaySec=</varname> is set, the system would regularly wake
first: low battery or the configured delay. If ACPI _BTP is not available, the system would up to check the time and detect the battery percentage/discharging rate. The rate is used to
regularly wake up to check the time and detect the battery percentage/discharging rate. The rate schedule the next detection. If that is also not available, <varname>SuspendEstimationSec=</varname>
is used to schedule the next detection. If that is also not available, is used as last resort.</para>
<varname>SuspendEstimationSec=</varname> is used as last resort.</para>
<xi:include href="version-info.xml" xpointer="v239"/> <xi:include href="version-info.xml" xpointer="v239"/>
</listitem> </listitem>

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later # SPDX-License-Identifier: LGPL-2.1-or-later
[Config] [Config]
MinimumVersion=commit:14d2d37a1923c03062f55454b2b61d0c64db6238 MinimumVersion=commit:e3642f81d3a7f8f9310c0c734b2ba9dd41e50e14
Dependencies= Dependencies=
initrd initrd
minimal-base minimal-base

View File

@ -787,7 +787,7 @@ static void job_emit_done_message(Unit *u, uint32_t job_id, JobType t, JobResult
log_unit_struct( log_unit_struct(
u, u,
job_done_messages[result].log_level, job_done_messages[result].log_level,
LOG_MESSAGE("%s skipped, no trigger condition checks were met.", LOG_MESSAGE("%s was skipped because no trigger condition checks were met.",
ident), ident),
LOG_ITEM("JOB_ID=%" PRIu32, job_id), LOG_ITEM("JOB_ID=%" PRIu32, job_id),
LOG_ITEM("JOB_TYPE=%s", job_type_to_string(t)), LOG_ITEM("JOB_TYPE=%s", job_type_to_string(t)),
@ -798,7 +798,7 @@ static void job_emit_done_message(Unit *u, uint32_t job_id, JobType t, JobResult
log_unit_struct( log_unit_struct(
u, u,
job_done_messages[result].log_level, job_done_messages[result].log_level,
LOG_MESSAGE("%s skipped, unmet condition check %s=%s%s", LOG_MESSAGE("%s was skipped because of an unmet condition check (%s=%s%s).",
ident, ident,
condition_type_to_string(c->type), condition_type_to_string(c->type),
c->negate ? "!" : "", c->negate ? "!" : "",

View File

@ -79,13 +79,16 @@ static int metrics_on_query_reply(
log_info("Varlink timed out"); log_info("Varlink timed out");
else else
log_error("Varlink error: %s", error_id); log_error("Varlink error: %s", error_id);
} else {
/* Collect metrics for later sorting */ goto finish;
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
return log_oom();
context->metrics[context->n_metrics++] = sd_json_variant_ref(parameters);
} }
/* Collect metrics for later sorting */
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
return log_oom();
context->metrics[context->n_metrics++] = sd_json_variant_ref(parameters);
finish:
if (!FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) { if (!FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) {
assert(context->n_open_connections > 0); assert(context->n_open_connections > 0);
context->n_open_connections--; context->n_open_connections--;
@ -108,7 +111,7 @@ static int metrics_call(const char *path, sd_event *event, sd_varlink **ret, Con
r = sd_varlink_connect_address(&vl, path); r = sd_varlink_connect_address(&vl, path);
if (r < 0) if (r < 0)
return log_error_errno(r, "Unable to connect to %s: %m", path); return log_debug_errno(r, "Unable to connect to %s: %m", path);
(void) sd_varlink_set_userdata(vl, context); (void) sd_varlink_set_userdata(vl, context);
@ -118,15 +121,15 @@ static int metrics_call(const char *path, sd_event *event, sd_varlink **ret, Con
r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL); r = sd_varlink_attach_event(vl, event, SD_EVENT_PRIORITY_NORMAL);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
r = sd_varlink_bind_reply(vl, metrics_on_query_reply); r = sd_varlink_bind_reply(vl, metrics_on_query_reply);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to bind reply callback: %m"); return log_debug_errno(r, "Failed to bind reply callback: %m");
r = sd_varlink_observe(vl, "io.systemd.Metrics.List", /* parameters= */ NULL); r = sd_varlink_observe(vl, "io.systemd.Metrics.List", /* parameters= */ NULL);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to issue io.systemd.Metrics.List call: %m"); return log_debug_errno(r, "Failed to issue io.systemd.Metrics.List call: %m");
*ret = TAKE_PTR(vl); *ret = TAKE_PTR(vl);
@ -153,6 +156,9 @@ static void context_done(Context *context) {
static void metrics_output_sorted(Context *context) { static void metrics_output_sorted(Context *context) {
assert(context); assert(context);
if (context->n_metrics == 0)
return;
typesafe_qsort(context->metrics, context->n_metrics, metric_compare); typesafe_qsort(context->metrics, context->n_metrics, metric_compare);
FOREACH_ARRAY(m, context->metrics, context->n_metrics) FOREACH_ARRAY(m, context->metrics, context->n_metrics)
@ -161,27 +167,26 @@ static void metrics_output_sorted(Context *context) {
SD_JSON_FORMAT_PRETTY_AUTO | SD_JSON_FORMAT_COLOR_AUTO | SD_JSON_FORMAT_FLUSH, SD_JSON_FORMAT_PRETTY_AUTO | SD_JSON_FORMAT_COLOR_AUTO | SD_JSON_FORMAT_FLUSH,
stdout, stdout,
NULL); NULL);
if (context->n_metrics == 0)
log_warning("No reporting sockets found.");
} }
static int metrics_query(void) { static int metrics_query(void) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_free_ char *metrics_path = NULL;
int r; int r;
_cleanup_free_ char *metrics_path = NULL;
r = runtime_directory_generic(arg_runtime_scope, "systemd/report", &metrics_path); r = runtime_directory_generic(arg_runtime_scope, "systemd/report", &metrics_path);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to determine metrics directory path: %m"); return log_error_errno(r, "Failed to determine metrics directory path: %m");
log_debug("Looking for reports in %s/", metrics_path); d = opendir(metrics_path);
if (!d) {
if (errno == ENOENT)
return 0;
_cleanup_closedir_ DIR *d = opendir(metrics_path); return log_error_errno(errno, "Failed to open directory %s: %m", metrics_path);
if (!d) }
return log_full_errno(errno == ENOENT ? LOG_WARNING : LOG_ERR, errno,
"Failed to open metrics directory %s: %m", metrics_path);
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
r = sd_event_default(&event); r = sd_event_default(&event);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to get event loop: %m"); return log_error_errno(r, "Failed to get event loop: %m");
@ -193,25 +198,27 @@ static int metrics_query(void) {
size_t n_varlinks = MAX_CONCURRENT_METRICS_SOCKETS; size_t n_varlinks = MAX_CONCURRENT_METRICS_SOCKETS;
sd_varlink **varlinks = new0(sd_varlink *, n_varlinks); sd_varlink **varlinks = new0(sd_varlink *, n_varlinks);
if (!varlinks) if (!varlinks)
return log_oom(); return log_error_errno(ENOMEM, "Failed to allocate varlinks array: %m");
CLEANUP_ARRAY(varlinks, n_varlinks, sd_varlink_unref_many); CLEANUP_ARRAY(varlinks, n_varlinks, sd_varlink_unref_many);
_cleanup_(context_done) Context context = {}; Context context = {};
FOREACH_DIRENT(de, d, FOREACH_DIRENT(de, d, return -errno) {
return log_warning_errno(errno, "Failed to read %s: %m", metrics_path)) { _cleanup_free_ char *p = NULL;
if (!IN_SET(de->d_type, DT_SOCK, DT_UNKNOWN)) if (!IN_SET(de->d_type, DT_SOCK, DT_UNKNOWN))
continue; continue;
_cleanup_free_ char *p = path_join(metrics_path, de->d_name); p = path_join(metrics_path, de->d_name);
if (!p) if (!p)
return log_oom(); return log_oom();
r = metrics_call(p, event, &varlinks[context.n_open_connections], &context); r = metrics_call(p, event, &varlinks[context.n_open_connections], &context);
if (r < 0) if (r < 0) {
log_error_errno(r, "Failed to connect to %s: %m", p);
continue; continue;
}
if (++context.n_open_connections >= MAX_CONCURRENT_METRICS_SOCKETS) { if (++context.n_open_connections >= MAX_CONCURRENT_METRICS_SOCKETS) {
log_warning("Too many concurrent metrics sockets, stop iterating"); log_warning("Too many concurrent metrics sockets, stop iterating");
@ -219,14 +226,16 @@ static int metrics_query(void) {
} }
} }
if (context.n_open_connections > 0) { r = sd_event_loop(event);
r = sd_event_loop(event); if (r < 0) {
if (r < 0) context_done(&context);
return log_error_errno(r, "Failed to run event loop: %m"); return log_error_errno(r, "Failed to run event loop: %m");
} }
metrics_output_sorted(&context); metrics_output_sorted(&context);
context_done(&context);
return 0; return 0;
} }
@ -309,7 +318,11 @@ static int run(int argc, char *argv[]) {
if (r <= 0) if (r <= 0)
return r; return r;
return metrics_query(); r = metrics_query();
if (r < 0)
return r;
return 0;
} }
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

View File

@ -507,90 +507,37 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) {
} }
static int execute_s2h(const SleepConfig *sleep_config) { static int execute_s2h(const SleepConfig *sleep_config) {
_cleanup_close_ int tfd = -EBADF;
usec_t hibernate_timestamp = 0;
int r; int r;
assert(sleep_config); assert(sleep_config);
/* Always check if we have automated battery alarms, regardless of HibernateDelaySec= setting. /* Only check if we have automated battery alarms if HibernateDelaySec= is not set, as in that case
* This allows both low-battery hibernation AND timeout-based hibernation to work together. */ * we'll busy poll for the configured interval instead */
r = check_wakeup_type(); if (!timestamp_is_set(sleep_config->hibernate_delay_usec)) {
if (r < 0) r = check_wakeup_type();
log_warning_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
else {
r = battery_trip_point_alarm_exists();
if (r < 0) if (r < 0)
log_warning_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m"); log_warning_errno(r, "Failed to check hardware wakeup type, ignoring: %m");
} else {
r = battery_trip_point_alarm_exists();
if (r > 0) { if (r < 0)
/* We have hardware battery alarm support (ACPI _BTP). If HibernateDelaySec= is also set, log_warning_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m");
* set up an RTC alarm so we hibernate on whichever comes first: low battery or timeout. */
if (timestamp_is_set(sleep_config->hibernate_delay_usec)) {
tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC);
if (tfd < 0)
return log_error_errno(errno, "Error creating timerfd: %m");
hibernate_timestamp = usec_add(now(CLOCK_BOOTTIME), sleep_config->hibernate_delay_usec);
} }
} else
r = 0; /* Force fallback path */
for (;;) { if (r > 0) { /* If we have both wakeup alarms and battery trip point support, use them */
if (tfd >= 0) { log_debug("Attempting to suspend...");
struct itimerspec ts = {}; r = execute(sleep_config, SLEEP_SUSPEND, NULL);
usec_t time_left; if (r < 0)
return r;
/* Handle HibernateOnACPower=no: reset timer while on AC power */ r = check_wakeup_type();
if (!sleep_config->hibernate_on_ac_power && on_ac_power() > 0) { if (r < 0)
log_debug("On AC power with HibernateOnACPower=no, resetting hibernate timer"); return log_error_errno(r, "Failed to check hardware wakeup type: %m");
hibernate_timestamp = usec_add(now(CLOCK_BOOTTIME), sleep_config->hibernate_delay_usec);
}
time_left = usec_sub_unsigned(hibernate_timestamp, now(CLOCK_BOOTTIME)); if (r == 0)
if (time_left <= 0) /* For APM Timer wakeup, system should hibernate else wakeup */
break; /* Timer expired, hibernate */
log_debug("Set timerfd wake alarm for %s",
FORMAT_TIMESPAN(time_left, USEC_PER_SEC));
timespec_store(&ts.it_value, time_left);
if (timerfd_settime(tfd, 0, &ts, NULL) < 0)
return log_error_errno(errno, "Error setting hibernate delay timer: %m");
}
log_debug("Attempting to suspend...");
r = execute(sleep_config, SLEEP_SUSPEND, NULL);
if (r < 0)
return r;
r = check_wakeup_type();
if (r < 0)
return log_error_errno(r, "Failed to check hardware wakeup type: %m");
if (r > 0) {
/* APM Timer wakeup - this means the battery alarm triggered, hibernate */
log_debug("Woken by APM Timer (battery low), proceeding to hibernate");
break;
}
if (tfd >= 0) {
/* Check if our HibernateDelaySec timer fired */
r = fd_wait_for_event(tfd, POLLIN, 0);
if (r < 0)
return log_error_errno(r, "Error polling timerfd: %m");
if (FLAGS_SET(r, POLLIN)) {
/* Timer fired - but respect HibernateOnACPower setting */
if (!sleep_config->hibernate_on_ac_power && on_ac_power() > 0) {
log_debug("Timer fired but on AC power with HibernateOnACPower=no, continuing suspend");
continue;
}
log_debug("HibernateDelaySec timeout reached, proceeding to hibernate");
break;
}
}
/* Manual wakeup - not battery alarm and not timer */
return 0; return 0;
}
} else { } else {
r = custom_timer_suspend(sleep_config); r = custom_timer_suspend(sleep_config);
if (r < 0) if (r < 0)

View File

@ -53,14 +53,9 @@ typedef enum EventState {
EVENT_QUEUED, EVENT_QUEUED,
EVENT_RUNNING, EVENT_RUNNING,
EVENT_LOCKED, EVENT_LOCKED,
EVENT_PROCESSED,
} EventState; } EventState;
typedef struct Event { typedef struct Event {
/* All events that have not been processed (state != EVENT_PROCESSED) are referenced by the Manager.
* Additionally, an event may be referenced by events blocked by this event. See event_find_blocker(). */
unsigned n_ref;
Manager *manager; Manager *manager;
Worker *worker; Worker *worker;
EventState state; EventState state;
@ -81,8 +76,9 @@ typedef struct Event {
char *whole_disk; char *whole_disk;
LIST_FIELDS(Event, same_disk); LIST_FIELDS(Event, same_disk);
/* The last blocker for this event. This event must not be processed before the blocker is processed. */ bool dependencies_built;
Event *blocker; Set *blocker_events;
Set *blocking_events;
LIST_FIELDS(Event, event); LIST_FIELDS(Event, event);
} Event; } Event;
@ -105,6 +101,21 @@ typedef struct Worker {
Event *event; Event *event;
} Worker; } Worker;
static void event_clear_dependencies(Event *event) {
assert(event);
Event *e;
while ((e = set_steal_first(event->blocker_events)))
assert_se(set_remove(e->blocking_events, event) == event);
event->blocker_events = set_free(event->blocker_events);
while ((e = set_steal_first(event->blocking_events)))
assert_se(set_remove(e->blocker_events, event) == event);
event->blocking_events = set_free(event->blocking_events);
event->dependencies_built = false;
}
static void event_unset_whole_disk(Event *event) { static void event_unset_whole_disk(Event *event) {
Manager *manager = ASSERT_PTR(ASSERT_PTR(event)->manager); Manager *manager = ASSERT_PTR(ASSERT_PTR(event)->manager);
@ -129,10 +140,6 @@ static void event_unset_whole_disk(Event *event) {
event->whole_disk = mfree(event->whole_disk); event->whole_disk = mfree(event->whole_disk);
} }
static Event* event_free(Event *event);
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(Event, event, event_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(Event*, event_unref);
static Event* event_free(Event *event) { static Event* event_free(Event *event) {
if (!event) if (!event)
return NULL; return NULL;
@ -149,25 +156,14 @@ static Event* event_free(Event *event) {
if (event->worker) if (event->worker)
event->worker->event = NULL; event->worker->event = NULL;
event_unref(event->blocker); event_clear_dependencies(event);
sd_device_unref(event->dev); sd_device_unref(event->dev);
return mfree(event); return mfree(event);
} }
static Event* event_enter_processed(Event *event) { DEFINE_TRIVIAL_CLEANUP_FUNC(Event*, event_free);
if (!event)
return NULL;
if (event->state == EVENT_PROCESSED)
return NULL;
event->state = EVENT_PROCESSED;
return event_unref(event);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(Event*, event_enter_processed);
static Worker* worker_free(Worker *worker) { static Worker* worker_free(Worker *worker) {
if (!worker) if (!worker)
@ -180,6 +176,7 @@ static Worker* worker_free(Worker *worker) {
sd_event_source_unref(worker->timeout_warning_event_source); sd_event_source_unref(worker->timeout_warning_event_source);
sd_event_source_unref(worker->timeout_kill_event_source); sd_event_source_unref(worker->timeout_kill_event_source);
pidref_done(&worker->pidref); pidref_done(&worker->pidref);
event_free(worker->event);
return mfree(worker); return mfree(worker);
} }
@ -203,8 +200,8 @@ Manager* manager_free(Manager *manager) {
udev_rules_free(manager->rules); udev_rules_free(manager->rules);
hashmap_free(manager->workers); hashmap_free(manager->workers);
LIST_FOREACH(event, event, manager->events) while (manager->events)
event_enter_processed(event); event_free(manager->events);
prioq_free(manager->locked_events_by_time); prioq_free(manager->locked_events_by_time);
hashmap_free(manager->locked_events_by_disk); hashmap_free(manager->locked_events_by_disk);
@ -395,85 +392,9 @@ void manager_revert(Manager *manager) {
manager_kill_workers(manager, SIGTERM); manager_kill_workers(manager, SIGTERM);
} }
static int on_worker_timeout_kill(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Manager *manager = ASSERT_PTR(worker->manager);
Event *event = ASSERT_PTR(worker->event);
(void) pidref_kill_and_sigcont(&worker->pidref, manager->config.timeout_signal);
worker->state = WORKER_KILLED;
log_device_error(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" killed.", worker->pidref.pid, event->seqnum);
return 0;
}
static int on_worker_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Event *event = ASSERT_PTR(worker->event);
log_device_warning(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" is taking a long time.", worker->pidref.pid, event->seqnum);
return 0;
}
static void worker_attach_event(Worker *worker, Event *event) {
Manager *manager = ASSERT_PTR(ASSERT_PTR(worker)->manager);
assert(event);
assert(event->state == EVENT_QUEUED);
assert(!event->worker);
assert(IN_SET(worker->state, WORKER_UNDEF, WORKER_IDLE));
assert(!worker->event);
worker->state = WORKER_RUNNING;
worker->event = event;
event->state = EVENT_RUNNING;
event->worker = worker;
(void) event_reset_time_relative(
manager->event,
&worker->timeout_warning_event_source,
CLOCK_MONOTONIC,
udev_warn_timeout(manager->config.timeout_usec),
USEC_PER_SEC,
on_worker_timeout_warning,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-warn",
/* force_reset= */ true);
(void) event_reset_time_relative(
manager->event,
&worker->timeout_kill_event_source,
CLOCK_MONOTONIC,
manager_kill_worker_timeout(manager),
USEC_PER_SEC,
on_worker_timeout_kill,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-kill",
/* force_reset= */ true);
}
static Event* worker_detach_event(Worker *worker) {
assert(worker);
Event *event = TAKE_PTR(worker->event);
if (event)
assert_se(TAKE_PTR(event->worker) == worker);
if (worker->state != WORKER_KILLED)
worker->state = WORKER_IDLE;
(void) event_source_disable(worker->timeout_warning_event_source);
(void) event_source_disable(worker->timeout_kill_event_source);
return event;
}
static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(worker_freep) Worker *worker = ASSERT_PTR(userdata); _cleanup_(worker_freep) Worker *worker = ASSERT_PTR(userdata);
_cleanup_(event_enter_processedp) Event *event = worker_detach_event(worker); sd_device *dev = worker->event ? ASSERT_PTR(worker->event->dev) : NULL;
sd_device *dev = event ? ASSERT_PTR(event->dev) : NULL;
assert(si); assert(si);
@ -551,6 +472,81 @@ static int worker_new(Worker **ret, Manager *manager, sd_device_monitor *worker_
return 0; return 0;
} }
static int on_worker_timeout_kill(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Manager *manager = ASSERT_PTR(worker->manager);
Event *event = ASSERT_PTR(worker->event);
(void) pidref_kill_and_sigcont(&worker->pidref, manager->config.timeout_signal);
worker->state = WORKER_KILLED;
log_device_error(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" killed.", worker->pidref.pid, event->seqnum);
return 0;
}
static int on_worker_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
Event *event = ASSERT_PTR(worker->event);
log_device_warning(event->dev, "Worker ["PID_FMT"] processing SEQNUM=%"PRIu64" is taking a long time.", worker->pidref.pid, event->seqnum);
return 0;
}
static void worker_attach_event(Worker *worker, Event *event) {
Manager *manager = ASSERT_PTR(ASSERT_PTR(worker)->manager);
assert(event);
assert(event->state == EVENT_QUEUED);
assert(!event->worker);
assert(IN_SET(worker->state, WORKER_UNDEF, WORKER_IDLE));
assert(!worker->event);
worker->state = WORKER_RUNNING;
worker->event = event;
event->state = EVENT_RUNNING;
event->worker = worker;
(void) event_reset_time_relative(
manager->event,
&worker->timeout_warning_event_source,
CLOCK_MONOTONIC,
udev_warn_timeout(manager->config.timeout_usec),
USEC_PER_SEC,
on_worker_timeout_warning,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-warn",
/* force_reset= */ true);
(void) event_reset_time_relative(
manager->event,
&worker->timeout_kill_event_source,
CLOCK_MONOTONIC,
manager_kill_worker_timeout(manager),
USEC_PER_SEC,
on_worker_timeout_kill,
worker,
EVENT_PRIORITY_WORKER_TIMER,
"worker-timeout-kill",
/* force_reset= */ true);
}
static Event* worker_detach_event(Worker *worker) {
assert(worker);
Event *event = TAKE_PTR(worker->event);
if (event)
assert_se(TAKE_PTR(event->worker) == worker);
if (worker->state != WORKER_KILLED)
worker->state = WORKER_IDLE;
(void) event_source_disable(worker->timeout_warning_event_source);
(void) event_source_disable(worker->timeout_kill_event_source);
return event;
}
static int worker_spawn(Manager *manager, Event *event) { static int worker_spawn(Manager *manager, Event *event) {
int r; int r;
@ -653,21 +649,17 @@ bool devpath_conflict(const char *a, const char *b) {
return *a == '/' || *b == '/' || *a == *b; return *a == '/' || *b == '/' || *a == *b;
} }
static void event_find_blocker(Event *event) { static int event_build_dependencies(Event *event) {
int r;
assert(event); assert(event);
/* lookup event for identical, parent, child device */ /* lookup event for identical, parent, child device */
if (event->blocker && event->blocker->state != EVENT_PROCESSED) if (event->dependencies_built)
/* Previously found blocker is not processed yet. */ return 0;
return;
/* If we have not found blocker yet, or the previously found blocker has been processed, let's find
* (another) blocker for this event. */
LIST_FOREACH_BACKWARDS(event, e, (event->blocker ?: event)->event_prev) {
if (e->state == EVENT_PROCESSED)
continue;
LIST_FOREACH_BACKWARDS(event, e, event->event_prev) {
if (!streq_ptr(event->id, e->id) && if (!streq_ptr(event->id, e->id) &&
!devpath_conflict(event->devpath, e->devpath) && !devpath_conflict(event->devpath, e->devpath) &&
!devpath_conflict(event->devpath, e->devpath_old) && !devpath_conflict(event->devpath, e->devpath_old) &&
@ -675,16 +667,22 @@ static void event_find_blocker(Event *event) {
!(event->devnode && streq_ptr(event->devnode, e->devnode))) !(event->devnode && streq_ptr(event->devnode, e->devnode)))
continue; continue;
r = set_ensure_put(&event->blocker_events, NULL, e);
if (r < 0)
return r;
r = set_ensure_put(&e->blocking_events, NULL, event);
if (r < 0) {
assert_se(set_remove(event->blocker_events, e) == e);
return r;
}
log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64, log_device_debug(event->dev, "SEQNUM=%" PRIu64 " blocked by SEQNUM=%" PRIu64,
event->seqnum, e->seqnum); event->seqnum, e->seqnum);
unref_and_replace_full(event->blocker, e, event_ref, event_unref);
return;
} }
/* No new blocker is found, and if set, previously found blocker has been processed. Clear the event->dependencies_built = true;
* previous blocker if set. */ return 0;
event->blocker = event_unref(event->blocker);
} }
static bool manager_can_process_event(Manager *manager) { static bool manager_can_process_event(Manager *manager) {
@ -741,10 +739,14 @@ static int event_queue_start(Manager *manager) {
if (event->state != EVENT_QUEUED) if (event->state != EVENT_QUEUED)
continue; continue;
event_find_blocker(event); r = event_build_dependencies(event);
if (r < 0)
log_device_warning_errno(event->dev, r,
"Failed to check dependencies for event (SEQNUM=%"PRIu64", ACTION=%s), ignoring: %m",
event->seqnum, strna(device_action_to_string(event->action)));
/* do not start event if parent or child event is still running or queued */ /* do not start event if parent or child event is still running or queued */
if (event->blocker) if (!set_isempty(event->blocker_events))
continue; continue;
r = event_run(event); r = event_run(event);
@ -925,12 +927,11 @@ static int event_queue_insert(Manager *manager, sd_device *dev) {
if (r < 0 && r != -ENOENT) if (r < 0 && r != -ENOENT)
return r; return r;
_cleanup_(event_unrefp) Event *event = new(Event, 1); _cleanup_(event_freep) Event *event = new(Event, 1);
if (!event) if (!event)
return -ENOMEM; return -ENOMEM;
*event = (Event) { *event = (Event) {
.n_ref = 1,
.dev = sd_device_ref(dev), .dev = sd_device_ref(dev),
.seqnum = seqnum, .seqnum = seqnum,
.action = action, .action = action,
@ -942,9 +943,6 @@ static int event_queue_insert(Manager *manager, sd_device *dev) {
.locked_event_prioq_index = PRIOQ_IDX_NULL, .locked_event_prioq_index = PRIOQ_IDX_NULL,
}; };
/* The kernel sometimes sends events in a wrong order, and we may receive an event with smaller
* SEQNUM after one with larger SEQNUM. To workaround the issue, let's reorder events if necessary. */
Event *prev = NULL; Event *prev = NULL;
LIST_FOREACH_BACKWARDS(event, e, manager->last_event) { LIST_FOREACH_BACKWARDS(event, e, manager->last_event) {
if (e->seqnum < event->seqnum) { if (e->seqnum < event->seqnum) {
@ -956,9 +954,8 @@ static int event_queue_insert(Manager *manager, sd_device *dev) {
"The event (SEQNUM=%"PRIu64") has been already queued.", "The event (SEQNUM=%"PRIu64") has been already queued.",
event->seqnum); event->seqnum);
/* The inserted event may be a blocker of an already queued event, hence the already found /* Inserting an event in an earlier place may change dependency tree. Let's rebuild it later. */
* blocker may not be the last one. Let's find the last blocker again later. */ event_clear_dependencies(e);
e->blocker = event_unref(e->blocker);
} }
LIST_INSERT_AFTER(event, manager->events, prev, event); LIST_INSERT_AFTER(event, manager->events, prev, event);
@ -1214,7 +1211,7 @@ static int on_worker_notify(sd_event_source *s, int fd, uint32_t revents, void *
return 0; return 0;
} }
_cleanup_(event_enter_processedp) Event *event = worker_detach_event(worker); _cleanup_(event_freep) Event *event = worker_detach_event(worker);
if (strv_contains(l, "TRY_AGAIN=1")) { if (strv_contains(l, "TRY_AGAIN=1")) {
/* Worker cannot lock the device. */ /* Worker cannot lock the device. */

View File

@ -43,7 +43,7 @@ def read_config():
def commit_file(files: list[Path], commit: str, changes: str): def commit_file(files: list[Path], commit: str, changes: str):
message = '\n'.join(( message = '\n'.join((
f'mkosi: update mkosi ref to {commit}', f'mkosi: update mkosi commit reference to {commit}',
'', '',
changes)) changes))