Skip to content

Commit 9ae50c7

Browse files
mrc0mmandbluca
authored andcommitted
sd-event: use CLOCK_BOOTTIME for rate limits
When a rate limit is armed, for example via StartLimitInterval=, and the machine is suspended, the rate limit's "clock" is suspended as well, since the elapse checks use CLOCK_MONOTONIC. This then causes unexpected situations where a rate limit armed via StartLimitInterval=1h, followed by a 10 hour suspend, would elapse after 11 hours total. Let's avoid this by switching the rate limits to CLOCK_BOOTTIME, which works the same as CLOCK_MONOTONIC, but accounts for the time spent in suspend as well. There's one slight concern when it comes to upgrade path - the old "begin" value of the rate limit is stored as CLOCK_MONOTONIC, but after upgrading systemd and serializing/deserializing the state it will be suddenly compared against now(CLOCK_BOOTTIME), which might cause some rate limits to elapse "prematurely". But this is just a one-time thing, after which the rate limit timers should re-assess themselves. Resolves: systemd#42912
1 parent 0168b13 commit 9ae50c7

5 files changed

Lines changed: 33 additions & 25 deletions

File tree

src/basic/ratelimit.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ bool ratelimit_below(RateLimit *rl) {
1414
if (!ratelimit_configured(rl))
1515
return true;
1616

17-
ts = now(CLOCK_MONOTONIC);
17+
ts = now(CLOCK_BOOTTIME);
1818

1919
if (rl->begin <= 0 ||
2020
usec_sub_unsigned(ts, rl->begin) > rl->interval) {
@@ -54,5 +54,5 @@ usec_t ratelimit_left(const RateLimit *rl) {
5454
if (rl->begin == 0)
5555
return 0;
5656

57-
return usec_sub_unsigned(ratelimit_end(rl), now(CLOCK_MONOTONIC));
57+
return usec_sub_unsigned(ratelimit_end(rl), now(CLOCK_BOOTTIME));
5858
}

src/core/manager.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1517,7 +1517,7 @@ static int manager_ratelimit_check_and_queue(Unit *u) {
15171517
r = sd_event_add_time(
15181518
u->manager->event,
15191519
&u->auto_start_stop_event_source,
1520-
CLOCK_MONOTONIC,
1520+
CLOCK_BOOTTIME,
15211521
ratelimit_end(&u->auto_start_stop_ratelimit),
15221522
0,
15231523
manager_ratelimit_requeue,

src/libsystemd/sd-event/sd-event.c

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ static void event_source_time_prioq_reshuffle(sd_event_source *s) {
900900
* properly again. */
901901

902902
if (s->ratelimited)
903-
d = &s->event->monotonic;
903+
d = &s->event->boottime;
904904
else if (EVENT_SOURCE_IS_TIME(s->type))
905905
assert_se(d = event_get_clock_data(s->event, s->type));
906906
else
@@ -950,7 +950,7 @@ static void source_disconnect(sd_event_source *s) {
950950
case SOURCE_TIME_BOOTTIME_ALARM:
951951
/* Only remove this event source from the time event source here if it is not ratelimited. If
952952
* it is ratelimited, we'll remove it below, separately. Why? Because the clock used might
953-
* differ: ratelimiting always uses CLOCK_MONOTONIC, but timer events might use any clock */
953+
* differ: ratelimiting always uses CLOCK_BOOTTIME, but timer events might use any clock */
954954

955955
if (!s->ratelimited) {
956956
struct clock_data *d;
@@ -1070,7 +1070,7 @@ static void source_disconnect(sd_event_source *s) {
10701070
prioq_remove(s->event->prepare, s, &s->prepare_index);
10711071

10721072
if (s->ratelimited)
1073-
event_source_time_prioq_remove(s, &s->event->monotonic);
1073+
event_source_time_prioq_remove(s, &s->event->boottime);
10741074

10751075
event = TAKE_PTR(s->event);
10761076
LIST_REMOVE(sources, event->sources, s);
@@ -3397,32 +3397,32 @@ static int event_source_enter_ratelimited(sd_event_source *s) {
33973397

33983398
assert(s);
33993399

3400-
/* When an event source becomes ratelimited, we place it in the CLOCK_MONOTONIC priority queue, with
3400+
/* When an event source becomes ratelimited, we place it in the CLOCK_BOOTTIME priority queue, with
34013401
* the end of the rate limit time window, much as if it was a timer event source. */
34023402

34033403
if (s->ratelimited)
34043404
return 0; /* Already ratelimited, this is a NOP hence */
34053405

3406-
/* Make sure we can install a CLOCK_MONOTONIC event further down. */
3407-
r = setup_clock_data(s->event, &s->event->monotonic, CLOCK_MONOTONIC);
3406+
/* Make sure we can install a CLOCK_BOOTTIME event further down. */
3407+
r = setup_clock_data(s->event, &s->event->boottime, CLOCK_BOOTTIME);
34083408
if (r < 0)
34093409
return r;
34103410

34113411
/* Timer event sources are already using the earliest/latest queues for the timer scheduling. Let's
34123412
* first remove them from the prioq appropriate for their own clock, so that we can use the prioq
3413-
* fields of the event source then for adding it to the CLOCK_MONOTONIC prioq instead. */
3413+
* fields of the event source then for adding it to the CLOCK_BOOTTIME prioq instead. */
34143414
if (EVENT_SOURCE_IS_TIME(s->type))
34153415
event_source_time_prioq_remove(s, event_get_clock_data(s->event, s->type));
34163416

3417-
/* Now, let's add the event source to the monotonic clock instead */
3418-
r = event_source_time_prioq_put(s, &s->event->monotonic);
3417+
/* Now, let's add the event source to the boottime clock instead */
3418+
r = event_source_time_prioq_put(s, &s->event->boottime);
34193419
if (r < 0)
34203420
goto fail;
34213421

34223422
/* And let's take the event source officially offline */
34233423
r = event_source_offline(s, s->enabled, /* ratelimited= */ true);
34243424
if (r < 0) {
3425-
event_source_time_prioq_remove(s, &s->event->monotonic);
3425+
event_source_time_prioq_remove(s, &s->event->boottime);
34263426
goto fail;
34273427
}
34283428

@@ -3448,8 +3448,8 @@ static int event_source_leave_ratelimit(sd_event_source *s, bool run_callback) {
34483448
if (!s->ratelimited)
34493449
return 0;
34503450

3451-
/* Let's take the event source out of the monotonic prioq first. */
3452-
event_source_time_prioq_remove(s, &s->event->monotonic);
3451+
/* Let's take the event source out of the boottime prioq first. */
3452+
event_source_time_prioq_remove(s, &s->event->boottime);
34533453

34543454
/* Let's then add the event source to its native clock prioq again — if this is a timer event source */
34553455
if (EVENT_SOURCE_IS_TIME(s->type)) {
@@ -3501,7 +3501,7 @@ static int event_source_leave_ratelimit(sd_event_source *s, bool run_callback) {
35013501
fail:
35023502
/* Do something somewhat reasonable when we cannot move an event sources out of ratelimited mode:
35033503
* simply put it back in it, maybe we can then process it more successfully next iteration. */
3504-
assert_se(event_source_time_prioq_put(s, &s->event->monotonic) >= 0);
3504+
assert_se(event_source_time_prioq_put(s, &s->event->boottime) >= 0);
35053505

35063506
return r;
35073507
}
@@ -4781,6 +4781,7 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t
47814781
}
47824782

47834783
_public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
4784+
bool ratelimit_expired = false;
47844785
int r;
47854786

47864787
assert_return(e, -EINVAL);
@@ -4845,6 +4846,8 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
48454846
r = process_timer(e, e->timestamp.boottime, &e->boottime);
48464847
if (r < 0)
48474848
goto finish;
4849+
else if (r == 1)
4850+
ratelimit_expired = true;
48484851

48494852
r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm);
48504853
if (r < 0)
@@ -4857,14 +4860,19 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
48574860
r = process_timer(e, e->timestamp.monotonic, &e->monotonic);
48584861
if (r < 0)
48594862
goto finish;
4860-
else if (r == 1) {
4861-
/* Ratelimit expiry callback was called. Let's postpone processing pending sources and
4862-
* put loop in the initial state in order to evaluate (in the next iteration) also sources
4863-
* there were potentially re-enabled by the callback.
4863+
4864+
if (ratelimit_expired) {
4865+
/* Ratelimit expiry callback was called. Let's postpone processing pending sources and put
4866+
* the loop in the initial state in order to evaluate (in the next iteration) also sources
4867+
* that were potentially re-enabled by the callback.
4868+
*
4869+
* Wondering why we treat only the CLOCK_BOOTTIME invocation of process_timer() differently?
4870+
* Once event source is ratelimited we essentially transform it into a CLOCK_BOOTTIME timer
4871+
* hence ratelimit expiry callback is never called for any other timer type.
48644872
*
4865-
* Wondering why we treat only this invocation of process_timer() differently? Once event
4866-
* source is ratelimited we essentially transform it into CLOCK_MONOTONIC timer hence
4867-
* ratelimit expiry callback is never called for any other timer type. */
4873+
* Note that we don't short-circuit earlier because all timer prioqs must be fully processed
4874+
* as their timerfds may have already been flushed by process_epoll(), so skipping them would
4875+
* leave those sources unprocessed and the timerfds un-rearmed. */
48684876
r = 0;
48694877
goto finish;
48704878
}

src/nsresourced/nsresourced-manager.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ static int start_workers(Manager *m, bool explicit_request) {
291291
r = sd_event_add_time(
292292
m->event,
293293
&m->deferred_start_worker_event_source,
294-
CLOCK_MONOTONIC,
294+
CLOCK_BOOTTIME,
295295
ratelimit_end(&m->worker_ratelimit),
296296
/* accuracy= */ 0,
297297
on_deferred_start_worker,

src/userdb/userdbd-manager.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ static int start_workers(Manager *m, bool explicit_request) {
259259
r = sd_event_add_time(
260260
m->event,
261261
&m->deferred_start_worker_event_source,
262-
CLOCK_MONOTONIC,
262+
CLOCK_BOOTTIME,
263263
ratelimit_end(&m->worker_ratelimit),
264264
/* accuracy= */ 0,
265265
on_deferred_start_worker,

0 commit comments

Comments
 (0)