Coverage Report

Created: 2026-06-15 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/cpu-limit.c
Line
Count
Source
1
/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "lib-signals.h"
5
#include "time-util.h"
6
#include "cpu-limit.h"
7
8
#include <sys/time.h>
9
#include <sys/resource.h>
10
11
struct cpu_limit {
12
  struct cpu_limit *parent;
13
14
  enum cpu_limit_type type;
15
  unsigned int cpu_limit_secs;
16
  struct rusage initial_usage;
17
18
  bool limit_reached;
19
};
20
21
static struct cpu_limit *cpu_limit;
22
static struct rlimit orig_limit, last_set_rlimit;
23
static volatile sig_atomic_t xcpu_signal_counter;
24
static sig_atomic_t checked_signal_counter;
25
static unsigned int rlim_cur_adjust_secs;
26
27
static void
28
cpu_limit_handler(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
29
0
{
30
0
  xcpu_signal_counter++;
31
0
}
32
33
static unsigned int
34
cpu_limit_get_usage_msecs_with(struct cpu_limit *climit,
35
             enum cpu_limit_type type,
36
             const struct rusage *rusage)
37
0
{
38
0
  struct timeval cpu_usage = { 0, 0 };
39
0
  long long usage_diff;
40
41
0
  if ((type & CPU_LIMIT_TYPE_USER) != 0)
42
0
    timeval_add(&cpu_usage, &rusage->ru_utime);
43
0
  if ((type & CPU_LIMIT_TYPE_SYSTEM) != 0)
44
0
    timeval_add(&cpu_usage, &rusage->ru_stime);
45
46
0
  struct timeval initial_total = { 0, 0 };
47
0
  if ((type & CPU_LIMIT_TYPE_USER) != 0)
48
0
    timeval_add(&initial_total, &climit->initial_usage.ru_utime);
49
0
  if ((type & CPU_LIMIT_TYPE_SYSTEM) != 0)
50
0
    timeval_add(&initial_total, &climit->initial_usage.ru_stime);
51
0
  usage_diff = timeval_diff_msecs(&cpu_usage, &initial_total);
52
0
  i_assert(usage_diff >= 0);
53
0
  i_assert(usage_diff <= UINT_MAX);
54
55
0
  return (unsigned int)usage_diff;
56
0
}
57
58
unsigned int
59
cpu_limit_get_usage_msecs(struct cpu_limit *climit, enum cpu_limit_type type)
60
0
{
61
0
  struct rusage rusage;
62
63
  /* Query cpu usage so far */
64
0
  if (getrusage(RUSAGE_SELF, &rusage) < 0)
65
0
    i_fatal("getrusage() failed: %m");
66
67
0
  return cpu_limit_get_usage_msecs_with(climit, type, &rusage);
68
0
}
69
70
static bool
71
cpu_limit_update_recursive(struct cpu_limit *climit,
72
         const struct rusage *rusage,
73
         unsigned int *max_wait_secs)
74
0
{
75
0
  if (climit == NULL)
76
0
    return FALSE;
77
0
  if (cpu_limit_update_recursive(climit->parent, rusage, max_wait_secs)) {
78
    /* parent's limit reached */
79
0
    climit->limit_reached = TRUE;
80
0
    return TRUE;
81
0
  }
82
0
  unsigned int secs_used =
83
0
    cpu_limit_get_usage_msecs_with(climit, climit->type, rusage)/1000;
84
0
  if (secs_used >= climit->cpu_limit_secs) {
85
0
    climit->limit_reached = TRUE;
86
0
    return TRUE;
87
0
  }
88
0
  unsigned int secs_left = climit->cpu_limit_secs - secs_used;
89
0
  if (*max_wait_secs > secs_left)
90
0
    *max_wait_secs = secs_left;
91
0
  return FALSE;
92
0
}
93
94
static void cpu_limit_update_rlimit(void)
95
0
{
96
0
  struct rusage rusage;
97
0
  struct rlimit rlimit;
98
0
  unsigned int max_wait_secs = UINT_MAX;
99
100
0
  if (getrusage(RUSAGE_SELF, &rusage) < 0)
101
0
    i_fatal("getrusage() failed: %m");
102
103
0
  (void)cpu_limit_update_recursive(cpu_limit, &rusage, &max_wait_secs);
104
0
  if (max_wait_secs == UINT_MAX) {
105
    /* All the limits have reached now. Restore the original
106
       limits. */
107
0
    rlimit = orig_limit;
108
0
  } else {
109
0
    struct timeval tv_limit = rusage.ru_utime;
110
0
    timeval_add(&tv_limit, &rusage.ru_stime);
111
112
0
    i_zero(&rlimit);
113
    /* Add +1 second to round up. */
114
0
    rlimit.rlim_cur = tv_limit.tv_sec +
115
0
      max_wait_secs + 1 + rlim_cur_adjust_secs;
116
0
    rlimit.rlim_max = orig_limit.rlim_max;
117
0
  }
118
0
  if (last_set_rlimit.rlim_cur != rlimit.rlim_cur) {
119
0
    last_set_rlimit = rlimit;
120
0
    if (setrlimit(RLIMIT_CPU, &rlimit) < 0)
121
0
      i_fatal("setrlimit() failed: %m");
122
0
  }
123
0
}
124
125
struct cpu_limit *
126
cpu_limit_init(unsigned int cpu_limit_secs, enum cpu_limit_type type)
127
0
{
128
0
  struct cpu_limit *climit;
129
0
  struct rusage rusage;
130
131
0
  i_assert(cpu_limit_secs > 0);
132
0
  i_assert(type != 0);
133
134
0
  climit = i_new(struct cpu_limit, 1);
135
0
  climit->parent = cpu_limit;
136
0
  climit->type = type;
137
0
  climit->cpu_limit_secs = cpu_limit_secs;
138
139
  /* Query current limit */
140
0
  if (climit->parent == NULL) {
141
0
    if (getrlimit(RLIMIT_CPU, &orig_limit) < 0)
142
0
      i_fatal("getrlimit() failed: %m");
143
0
  }
144
145
  /* Query cpu usage so far */
146
0
  if (getrusage(RUSAGE_SELF, &rusage) < 0)
147
0
    i_fatal("getrusage() failed: %m");
148
0
  climit->initial_usage = rusage;
149
150
0
  if (climit->parent == NULL) {
151
0
    lib_signals_set_handler(SIGXCPU, LIBSIG_FLAG_RESTART,
152
0
          cpu_limit_handler, NULL);
153
0
  }
154
155
0
  cpu_limit = climit;
156
0
  cpu_limit_update_rlimit();
157
0
  return climit;
158
0
}
159
160
void cpu_limit_deinit(struct cpu_limit **_climit)
161
0
{
162
0
  struct cpu_limit *climit = *_climit;
163
164
0
  *_climit = NULL;
165
0
  if (climit == NULL)
166
0
    return;
167
168
0
  i_assert(climit == cpu_limit);
169
170
0
  cpu_limit = climit->parent;
171
0
  cpu_limit_update_rlimit();
172
0
  if (climit->parent == NULL)
173
0
    lib_signals_unset_handler(SIGXCPU, cpu_limit_handler, NULL);
174
0
  i_free(climit);
175
0
}
176
177
bool cpu_limit_exceeded(struct cpu_limit *climit)
178
0
{
179
0
  static struct timeval tv_last = { 0, 0 };
180
0
  struct timeval tv_now;
181
182
0
  if (checked_signal_counter != xcpu_signal_counter) {
183
0
    i_gettimeofday(&tv_now);
184
0
    if (tv_last.tv_sec != 0 &&
185
0
        timeval_diff_msecs(&tv_now, &tv_last) < 1000) {
186
      /* Additional sanity check: We're getting here more
187
         rapidly than once per second. This isn't expected
188
         to happen, but at least in theory it could happen
189
         because rlim_cur isn't clearly calculated from just
190
         the user+system CPU usage. So in case rlim_cur is
191
         too low and keeps firing XCPU signal, try to
192
         increase rlim_cur by 1 second. Eventually it should
193
         become large enough. */
194
0
      rlim_cur_adjust_secs++;
195
0
    }
196
197
0
    checked_signal_counter = xcpu_signal_counter;
198
0
    cpu_limit_update_rlimit();
199
0
  }
200
0
  return climit->limit_reached;
201
0
}