]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/test/test-mempress.c
man/systemd.mount: tmpfs automatically gains After=swap.target dep
[thirdparty/systemd.git] / src / test / test-mempress.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <fcntl.h>
4 #include <pthread.h>
5 #include <sys/mman.h>
6 #include <unistd.h>
7
8 #include <sd-bus.h>
9 #include <sd-event.h>
10
11 #include "bus-locator.h"
12 #include "bus-wait-for-jobs.h"
13 #include "fd-util.h"
14 #include "path-util.h"
15 #include "process-util.h"
16 #include "random-util.h"
17 #include "rm-rf.h"
18 #include "signal-util.h"
19 #include "socket-util.h"
20 #include "tests.h"
21 #include "tmpfile-util.h"
22 #include "unit-def.h"
23
24 struct fake_pressure_context {
25 int fifo_fd;
26 int socket_fd;
27 };
28
29 static void *fake_pressure_thread(void *p) {
30 _cleanup_free_ struct fake_pressure_context *c = ASSERT_PTR(p);
31 _cleanup_close_ int cfd = -EBADF;
32
33 usleep_safe(150);
34
35 assert_se(write(c->fifo_fd, &(const char) { 'x' }, 1) == 1);
36
37 usleep_safe(150);
38
39 cfd = accept4(c->socket_fd, NULL, NULL, SOCK_CLOEXEC);
40 assert_se(cfd >= 0);
41 char buf[STRLEN("hello")+1] = {};
42 assert_se(read(cfd, buf, sizeof(buf)-1) == sizeof(buf)-1);
43 assert_se(streq(buf, "hello"));
44 assert_se(write(cfd, &(const char) { 'z' }, 1) == 1);
45
46 return 0;
47 }
48
49 static int fake_pressure_callback(sd_event_source *s, void *userdata) {
50 int *value = userdata;
51 const char *d;
52
53 assert_se(s);
54 assert_se(sd_event_source_get_description(s, &d) >= 0);
55
56 *value *= d[0];
57
58 log_notice("memory pressure event: %s", d);
59
60 if (*value == 7 * 'f' * 's')
61 assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0);
62
63 return 0;
64 }
65
66 TEST(fake_pressure) {
67 _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *ef = NULL;
68 _cleanup_(sd_event_unrefp) sd_event *e = NULL;
69 _cleanup_free_ char *j = NULL, *k = NULL;
70 _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL;
71 _cleanup_close_ int fifo_fd = -EBADF, socket_fd = -EBADF;
72 union sockaddr_union sa;
73 pthread_t th;
74 int value = 7;
75
76 assert_se(sd_event_default(&e) >= 0);
77
78 assert_se(mkdtemp_malloc(NULL, &tmp) >= 0);
79
80 assert_se(j = path_join(tmp, "fifo"));
81 assert_se(mkfifo(j, 0600) >= 0);
82 fifo_fd = open(j, O_CLOEXEC|O_RDWR|O_NONBLOCK);
83 assert_se(fifo_fd >= 0);
84
85 assert_se(k = path_join(tmp, "sock"));
86 socket_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
87 assert_se(socket_fd >= 0);
88 assert_se(sockaddr_un_set_path(&sa.un, k) >= 0);
89 assert_se(bind(socket_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) >= 0);
90 assert_se(listen(socket_fd, 1) >= 0);
91
92 /* Ideally we'd just allocate this on the stack, but AddressSanitizer doesn't like it if threads
93 * access each other's stack */
94 struct fake_pressure_context *fp = new(struct fake_pressure_context, 1);
95 assert_se(fp);
96 *fp = (struct fake_pressure_context) {
97 .fifo_fd = fifo_fd,
98 .socket_fd = socket_fd,
99 };
100
101 assert_se(pthread_create(&th, NULL, fake_pressure_thread, TAKE_PTR(fp)) == 0);
102
103 assert_se(setenv("MEMORY_PRESSURE_WATCH", j, /* override= */ true) >= 0);
104 assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0);
105
106 assert_se(sd_event_add_memory_pressure(e, &es, fake_pressure_callback, &value) >= 0);
107 assert_se(sd_event_source_set_description(es, "fifo event source") >= 0);
108
109 assert_se(setenv("MEMORY_PRESSURE_WATCH", k, /* override= */ true) >= 0);
110 assert_se(setenv("MEMORY_PRESSURE_WRITE", "aGVsbG8K", /* override= */ true) >= 0);
111
112 assert_se(sd_event_add_memory_pressure(e, &ef, fake_pressure_callback, &value) >= 0);
113 assert_se(sd_event_source_set_description(ef, "socket event source") >= 0);
114
115 assert_se(sd_event_loop(e) >= 0);
116
117 assert_se(value == 7 * 'f' * 's');
118
119 assert_se(pthread_join(th, NULL) == 0);
120 }
121
122 struct real_pressure_context {
123 sd_event_source *pid;
124 };
125
126 static int real_pressure_callback(sd_event_source *s, void *userdata) {
127 struct real_pressure_context *c = ASSERT_PTR(userdata);
128 const char *d;
129
130 assert_se(s);
131 assert_se(sd_event_source_get_description(s, &d) >= 0);
132
133 log_notice("real_memory pressure event: %s", d);
134
135 sd_event_trim_memory();
136
137 assert_se(c->pid);
138 assert_se(sd_event_source_send_child_signal(c->pid, SIGKILL, NULL, 0) >= 0);
139 c->pid = NULL;
140
141 return 0;
142 }
143
144 #define MMAP_SIZE (10 * 1024 * 1024)
145
146 _noreturn_ static void real_pressure_eat_memory(int pipe_fd) {
147 size_t ate = 0;
148
149 /* Allocates and touches 10M at a time, until runs out of memory */
150
151 char x;
152 assert_se(read(pipe_fd, &x, 1) == 1); /* Wait for the GO! */
153
154 for (;;) {
155 void *p;
156
157 p = mmap(NULL, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
158 assert_se(p != MAP_FAILED);
159
160 log_info("Eating another %s.", FORMAT_BYTES(MMAP_SIZE));
161
162 memset(p, random_u32() & 0xFF, MMAP_SIZE);
163 ate += MMAP_SIZE;
164
165 log_info("Ate %s in total.", FORMAT_BYTES(ate));
166
167 usleep_safe(50 * USEC_PER_MSEC);
168 }
169 }
170
171 static int real_pressure_child_callback(sd_event_source *s, const siginfo_t *si, void *userdata) {
172 assert_se(s);
173 assert_se(si);
174
175 log_notice("child dead");
176
177 assert_se(si->si_signo == SIGCHLD);
178 assert_se(si->si_status == SIGKILL);
179 assert_se(si->si_code == CLD_KILLED);
180
181 assert_se(sd_event_exit(sd_event_source_get_event(s), 31) >= 0);
182 return 0;
183 }
184
185 TEST(real_pressure) {
186 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
187 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
188 _cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL, *cs = NULL;
189 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
190 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
191 _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR;
192 _cleanup_(sd_event_unrefp) sd_event *e = NULL;
193 _cleanup_free_ char *scope = NULL;
194 const char *object;
195 int r;
196 pid_t pid;
197
198 r = sd_bus_open_system(&bus);
199 if (r < 0) {
200 log_notice_errno(r, "Can't connect to system bus, skipping test: %m");
201 return;
202 }
203
204 assert_se(bus_wait_for_jobs_new(bus, &w) >= 0);
205
206 assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit") >= 0);
207 assert_se(asprintf(&scope, "test-%" PRIu64 ".scope", random_u64()) >= 0);
208 assert_se(sd_bus_message_append(m, "ss", scope, "fail") >= 0);
209 assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0);
210 assert_se(sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, 0) >= 0);
211 assert_se(sd_bus_message_append(m, "(sv)", "MemoryAccounting", "b", true) >= 0);
212 assert_se(sd_bus_message_close_container(m) >= 0);
213 assert_se(sd_bus_message_append(m, "a(sa(sv))", 0) >= 0);
214
215 r = sd_bus_call(bus, m, 0, &error, &reply);
216 if (r < 0) {
217 log_notice_errno(r, "Can't issue transient unit call, skipping test: %m");
218 return;
219 }
220
221 assert_se(sd_bus_message_read(reply, "o", &object) >= 0);
222
223 assert_se(bus_wait_for_jobs_one(w, object, /* quiet= */ false, /* extra_args= */ NULL) >= 0);
224
225 assert_se(sd_event_default(&e) >= 0);
226
227 assert_se(pipe2(pipe_fd, O_CLOEXEC) >= 0);
228
229 r = safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pid);
230 assert_se(r >= 0);
231 if (r == 0) {
232 real_pressure_eat_memory(pipe_fd[0]);
233 _exit(EXIT_SUCCESS);
234 }
235
236 assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
237 assert_se(sd_event_add_child(e, &cs, pid, WEXITED, real_pressure_child_callback, NULL) >= 0);
238 assert_se(sd_event_source_set_child_process_own(cs, true) >= 0);
239
240 assert_se(unsetenv("MEMORY_PRESSURE_WATCH") >= 0);
241 assert_se(unsetenv("MEMORY_PRESSURE_WRITE") >= 0);
242
243 struct real_pressure_context context = {
244 .pid = cs,
245 };
246
247 r = sd_event_add_memory_pressure(e, &es, real_pressure_callback, &context);
248 if (r < 0) {
249 log_notice_errno(r, "Can't allocate memory pressure fd, skipping test: %m");
250 return;
251 }
252
253 assert_se(sd_event_source_set_description(es, "real pressure event source") >= 0);
254 assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0);
255 assert_se(sd_event_source_set_memory_pressure_type(es, "full") > 0);
256 assert_se(sd_event_source_set_memory_pressure_type(es, "full") == 0);
257 assert_se(sd_event_source_set_memory_pressure_type(es, "some") > 0);
258 assert_se(sd_event_source_set_memory_pressure_type(es, "some") == 0);
259 assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) > 0);
260 assert_se(sd_event_source_set_memory_pressure_period(es, 70 * USEC_PER_MSEC, USEC_PER_SEC) == 0);
261 assert_se(sd_event_source_set_enabled(es, SD_EVENT_ONESHOT) >= 0);
262
263 _cleanup_free_ char *uo = NULL;
264 assert_se(uo = unit_dbus_path_from_name(scope));
265
266 uint64_t mcurrent = UINT64_MAX;
267 assert_se(sd_bus_get_property_trivial(bus, "org.freedesktop.systemd1", uo, "org.freedesktop.systemd1.Scope", "MemoryCurrent", &error, 't', &mcurrent) >= 0);
268
269 printf("current: %" PRIu64 "\n", mcurrent);
270 if (mcurrent == UINT64_MAX) {
271 log_notice_errno(r, "Memory accounting not available, skipping test: %m");
272 return;
273 }
274
275 m = sd_bus_message_unref(m);
276
277 assert_se(bus_message_new_method_call(bus, &m, bus_systemd_mgr, "SetUnitProperties") >= 0);
278 assert_se(sd_bus_message_append(m, "sb", scope, true) >= 0);
279 assert_se(sd_bus_message_open_container(m, 'a', "(sv)") >= 0);
280 assert_se(sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", mcurrent + (15 * 1024 * 1024)) >= 0);
281 assert_se(sd_bus_message_append(m, "(sv)", "MemoryMax", "t", mcurrent + (50 * 1024 * 1024)) >= 0);
282 assert_se(sd_bus_message_close_container(m) >= 0);
283
284 assert_se(sd_bus_call(bus, m, 0, NULL, NULL) >= 0);
285
286 /* Generate some memory allocations via mempool */
287 #define NN (1024)
288 Hashmap **h = new(Hashmap*, NN);
289 for (int i = 0; i < NN; i++)
290 h[i] = hashmap_new(NULL);
291 for (int i = 0; i < NN; i++)
292 hashmap_free(h[i]);
293 free(h);
294
295 /* Now start eating memory */
296 assert_se(write(pipe_fd[1], &(const char) { 'x' }, 1) == 1);
297
298 assert_se(sd_event_loop(e) >= 0);
299 int ex = 0;
300 assert_se(sd_event_get_exit_code(e, &ex) >= 0);
301 assert_se(ex == 31);
302 }
303
304 static int outro(void) {
305 hashmap_trim_pools();
306 return 0;
307 }
308
309 DEFINE_TEST_MAIN_FULL(LOG_DEBUG, NULL, outro);