]>
Commit | Line | Data |
---|---|---|
b7dc40e6 LP |
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); | |
92651a7a | 31 | _cleanup_close_ int cfd = -EBADF; |
b7dc40e6 | 32 | |
4251512e | 33 | usleep_safe(150); |
b7dc40e6 LP |
34 | |
35 | assert_se(write(c->fifo_fd, &(const char) { 'x' }, 1) == 1); | |
36 | ||
4251512e | 37 | usleep_safe(150); |
b7dc40e6 LP |
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; | |
92651a7a | 71 | _cleanup_close_ int fifo_fd = -EBADF, socket_fd = -EBADF; |
b7dc40e6 LP |
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 | ||
4251512e | 167 | usleep_safe(50 * USEC_PER_MSEC); |
b7dc40e6 LP |
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; | |
71136404 | 191 | _cleanup_close_pair_ int pipe_fd[2] = EBADF_PAIR; |
b7dc40e6 LP |
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 | ||
e22ad53d | 223 | assert_se(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL) >= 0); |
b7dc40e6 LP |
224 | |
225 | assert_se(sd_event_default(&e) >= 0); | |
226 | ||
227 | assert_se(pipe2(pipe_fd, O_CLOEXEC) >= 0); | |
228 | ||
e9ccae31 | 229 | r = safe_fork("(eat-memory)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &pid); |
b7dc40e6 LP |
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 | ||
7e48f3ba | 263 | _cleanup_free_ char *uo = NULL; |
b7dc40e6 LP |
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); |