]>
Commit | Line | Data |
---|---|---|
d9a64523 A |
1 | #include <stdio.h> |
2 | #include <signal.h> | |
c3c9b80d | 3 | #include <sys/proc.h> |
d9a64523 A |
4 | #include <sys/sysctl.h> |
5 | #include <sys/kern_memorystatus.h> | |
f427ee49 | 6 | #include <sys/kern_memorystatus_freeze.h> |
ea3f0419 | 7 | #include <time.h> |
d9a64523 | 8 | #include <mach-o/dyld.h> |
cb323159 A |
9 | #include <mach/mach_vm.h> |
10 | #include <mach/vm_page_size.h> /* Needed for vm_region info */ | |
11 | #include <mach/shared_region.h> | |
12 | #include <mach/mach.h> | |
d9a64523 A |
13 | |
14 | #ifdef T_NAMESPACE | |
15 | #undef T_NAMESPACE | |
16 | #endif | |
17 | #include <darwintest.h> | |
18 | #include <darwintest_utils.h> | |
19 | ||
cb323159 A |
20 | #include "memorystatus_assertion_helpers.h" |
21 | ||
d9a64523 A |
22 | T_GLOBAL_META( |
23 | T_META_NAMESPACE("xnu.vm"), | |
24 | T_META_CHECK_LEAKS(false) | |
0a7de745 | 25 | ); |
d9a64523 | 26 | |
0a7de745 A |
27 | #define MEM_SIZE_MB 10 |
28 | #define NUM_ITERATIONS 5 | |
cb323159 | 29 | #define FREEZE_PAGES_MAX 256 |
d9a64523 A |
30 | |
31 | #define CREATE_LIST(X) \ | |
32 | X(SUCCESS) \ | |
33 | X(TOO_FEW_ARGUMENTS) \ | |
34 | X(SYSCTL_VM_PAGESIZE_FAILED) \ | |
35 | X(VM_PAGESIZE_IS_ZERO) \ | |
d9a64523 A |
36 | X(DISPATCH_SOURCE_CREATE_FAILED) \ |
37 | X(INITIAL_SIGNAL_TO_PARENT_FAILED) \ | |
38 | X(SIGNAL_TO_PARENT_FAILED) \ | |
39 | X(MEMORYSTATUS_CONTROL_FAILED) \ | |
40 | X(IS_FREEZABLE_NOT_AS_EXPECTED) \ | |
41 | X(MEMSTAT_PRIORITY_CHANGE_FAILED) \ | |
ea3f0419 | 42 | X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \ |
2a1bd2d3 A |
43 | X(FROZEN_BIT_SET) \ |
44 | X(FROZEN_BIT_NOT_SET) \ | |
45 | X(MEMORYSTATUS_CONTROL_ERROR) \ | |
46 | X(EXIT_CODE_MAX) \ | |
d9a64523 A |
47 | |
48 | #define EXIT_CODES_ENUM(VAR) VAR, | |
49 | enum exit_codes_num { | |
50 | CREATE_LIST(EXIT_CODES_ENUM) | |
51 | }; | |
52 | ||
53 | #define EXIT_CODES_STRING(VAR) #VAR, | |
54 | static const char *exit_codes_str[] = { | |
55 | CREATE_LIST(EXIT_CODES_STRING) | |
56 | }; | |
57 | ||
cb323159 A |
58 | static int |
59 | get_vmpage_size() | |
60 | { | |
61 | int vmpage_size; | |
62 | size_t size = sizeof(vmpage_size); | |
63 | int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0); | |
64 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize"); | |
65 | T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0"); | |
66 | return vmpage_size; | |
67 | } | |
d9a64523 | 68 | |
cb323159 | 69 | static pid_t child_pid = -1; |
d9a64523 A |
70 | static int freeze_count = 0; |
71 | ||
f427ee49 | 72 | void move_to_idle_band(pid_t); |
cb323159 | 73 | void run_freezer_test(int); |
d9a64523 | 74 | void freeze_helper_process(void); |
cb323159 A |
75 | /* Gets and optionally sets the freeze pages max threshold */ |
76 | int sysctl_freeze_pages_max(int* new_value); | |
77 | ||
78 | /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test. | |
79 | * We're moving away from those unit tests, so they're copied here. | |
80 | */ | |
81 | ||
82 | /* Cribbed from 'top'... */ | |
83 | static int | |
84 | in_shared_region(mach_vm_address_t addr, cpu_type_t type) | |
85 | { | |
86 | mach_vm_address_t base = 0, size = 0; | |
87 | ||
88 | switch (type) { | |
89 | case CPU_TYPE_ARM: | |
90 | base = SHARED_REGION_BASE_ARM; | |
91 | size = SHARED_REGION_SIZE_ARM; | |
92 | break; | |
93 | ||
94 | case CPU_TYPE_ARM64: | |
95 | base = SHARED_REGION_BASE_ARM64; | |
96 | size = SHARED_REGION_SIZE_ARM64; | |
97 | break; | |
98 | ||
99 | ||
100 | case CPU_TYPE_X86_64: | |
101 | base = SHARED_REGION_BASE_X86_64; | |
102 | size = SHARED_REGION_SIZE_X86_64; | |
103 | break; | |
104 | ||
105 | case CPU_TYPE_I386: | |
106 | base = SHARED_REGION_BASE_I386; | |
107 | size = SHARED_REGION_SIZE_I386; | |
108 | break; | |
109 | ||
110 | case CPU_TYPE_POWERPC: | |
111 | base = SHARED_REGION_BASE_PPC; | |
112 | size = SHARED_REGION_SIZE_PPC; | |
113 | break; | |
114 | ||
115 | case CPU_TYPE_POWERPC64: | |
116 | base = SHARED_REGION_BASE_PPC64; | |
117 | size = SHARED_REGION_SIZE_PPC64; | |
118 | break; | |
119 | ||
120 | default: { | |
121 | int t = type; | |
122 | ||
123 | fprintf(stderr, "unknown CPU type: 0x%x\n", t); | |
124 | abort(); | |
125 | } | |
126 | } | |
127 | ||
128 | return addr >= base && addr < (base + size); | |
129 | } | |
130 | ||
131 | /* Get the resident private memory of the given pid */ | |
132 | static unsigned long long | |
133 | get_rprvt(pid_t pid) | |
134 | { | |
135 | mach_port_name_t task; | |
136 | kern_return_t kr; | |
137 | ||
138 | mach_vm_size_t rprvt = 0; | |
139 | mach_vm_size_t empty = 0; | |
140 | mach_vm_size_t fw_private = 0; | |
141 | mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported | |
142 | // in terms of vm_kernel_page_size. | |
143 | mach_vm_size_t regs = 0; | |
144 | ||
145 | mach_vm_address_t addr; | |
146 | mach_vm_size_t size; | |
147 | ||
148 | int split = 0; | |
149 | ||
150 | kr = task_for_pid(mach_task_self(), pid, &task); | |
151 | T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child"); | |
152 | ||
153 | for (addr = 0;; addr += size) { | |
154 | vm_region_top_info_data_t info; | |
155 | mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT; | |
156 | mach_port_t object_name; | |
157 | ||
158 | kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name); | |
159 | if (kr != KERN_SUCCESS) { | |
160 | break; | |
161 | } | |
162 | ||
163 | #if defined (__arm64__) | |
164 | if (in_shared_region(addr, CPU_TYPE_ARM64)) { | |
165 | #else | |
166 | if (in_shared_region(addr, CPU_TYPE_ARM)) { | |
167 | #endif | |
168 | // Private Shared | |
169 | fw_private += info.private_pages_resident * pagesize; | |
170 | ||
171 | /* | |
172 | * Check if this process has the globally shared | |
173 | * text and data regions mapped in. If so, set | |
174 | * split to TRUE and avoid checking | |
175 | * again. | |
176 | */ | |
177 | if (split == FALSE && info.share_mode == SM_EMPTY) { | |
178 | vm_region_basic_info_data_64_t b_info; | |
179 | mach_vm_address_t b_addr = addr; | |
180 | mach_vm_size_t b_size = size; | |
181 | count = VM_REGION_BASIC_INFO_COUNT_64; | |
182 | ||
183 | kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name); | |
184 | if (kr != KERN_SUCCESS) { | |
185 | break; | |
186 | } | |
187 | ||
188 | if (b_info.reserved) { | |
189 | split = TRUE; | |
190 | } | |
191 | } | |
192 | ||
193 | /* | |
194 | * Short circuit the loop if this isn't a shared | |
195 | * private region, since that's the only region | |
196 | * type we care about within the current address | |
197 | * range. | |
198 | */ | |
199 | if (info.share_mode != SM_PRIVATE) { | |
200 | continue; | |
201 | } | |
202 | } | |
d9a64523 | 203 | |
cb323159 A |
204 | regs++; |
205 | ||
206 | /* | |
207 | * Update counters according to the region type. | |
208 | */ | |
209 | ||
210 | if (info.share_mode == SM_COW && info.ref_count == 1) { | |
211 | // Treat single reference SM_COW as SM_PRIVATE | |
212 | info.share_mode = SM_PRIVATE; | |
213 | } | |
214 | ||
215 | switch (info.share_mode) { | |
216 | case SM_LARGE_PAGE: | |
217 | // Treat SM_LARGE_PAGE the same as SM_PRIVATE | |
218 | // since they are not shareable and are wired. | |
219 | case SM_PRIVATE: | |
220 | rprvt += info.private_pages_resident * pagesize; | |
221 | rprvt += info.shared_pages_resident * pagesize; | |
222 | break; | |
223 | ||
224 | case SM_EMPTY: | |
225 | empty += size; | |
226 | break; | |
227 | ||
228 | case SM_COW: | |
229 | case SM_SHARED: | |
230 | if (pid == 0) { | |
231 | // Treat kernel_task specially | |
232 | if (info.share_mode == SM_COW) { | |
233 | rprvt += info.private_pages_resident * pagesize; | |
234 | } | |
235 | break; | |
236 | } | |
237 | ||
238 | if (info.share_mode == SM_COW) { | |
239 | rprvt += info.private_pages_resident * pagesize; | |
240 | } | |
241 | break; | |
242 | ||
243 | default: | |
244 | assert(0); | |
245 | break; | |
246 | } | |
247 | } | |
248 | ||
249 | return rprvt; | |
250 | } | |
d9a64523 | 251 | |
0a7de745 | 252 | void |
f427ee49 | 253 | move_to_idle_band(pid_t pid) |
0a7de745 | 254 | { |
d9a64523 A |
255 | memorystatus_priority_properties_t props; |
256 | /* | |
257 | * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits. | |
258 | * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario. | |
259 | */ | |
260 | props.priority = JETSAM_PRIORITY_IDLE; | |
261 | props.user_data = 0; | |
262 | ||
263 | /* | |
264 | * This requires us to run as root (in the absence of entitlement). | |
265 | * Hence the T_META_ASROOT(true) in the T_HELPER_DECL. | |
266 | */ | |
f427ee49 | 267 | if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props))) { |
d9a64523 A |
268 | exit(MEMSTAT_PRIORITY_CHANGE_FAILED); |
269 | } | |
270 | } | |
271 | ||
0a7de745 A |
272 | void |
273 | freeze_helper_process(void) | |
274 | { | |
275 | size_t length; | |
276 | int ret, freeze_enabled, errno_freeze_sysctl; | |
cb323159 A |
277 | uint64_t resident_memory_before, resident_memory_after, vmpage_size; |
278 | vmpage_size = (uint64_t) get_vmpage_size(); | |
279 | resident_memory_before = get_rprvt(child_pid) / vmpage_size; | |
d9a64523 | 280 | |
cb323159 A |
281 | T_LOG("Freezing child pid %d", child_pid); |
282 | ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid)); | |
0a7de745 | 283 | errno_freeze_sysctl = errno; |
d9a64523 A |
284 | sleep(1); |
285 | ||
0a7de745 A |
286 | /* |
287 | * The child process toggles its freezable state on each iteration. | |
288 | * So a failure for every alternate freeze is expected. | |
289 | */ | |
cb323159 | 290 | if (freeze_count % 2) { |
0a7de745 A |
291 | length = sizeof(freeze_enabled); |
292 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0), | |
293 | "failed to query vm.freeze_enabled"); | |
294 | if (freeze_enabled) { | |
295 | errno = errno_freeze_sysctl; | |
296 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed"); | |
297 | } else { | |
298 | /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */ | |
299 | T_LOG("Freeze has been disabled. Terminating early."); | |
300 | T_END; | |
301 | } | |
cb323159 A |
302 | resident_memory_after = get_rprvt(child_pid) / vmpage_size; |
303 | uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL); | |
304 | T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set"); | |
305 | if (resident_memory_before > freeze_pages_max) { | |
306 | T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold."); | |
307 | } | |
308 | ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid)); | |
d9a64523 A |
309 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed"); |
310 | } else { | |
311 | T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed"); | |
312 | T_LOG("Freeze failed as expected"); | |
313 | } | |
314 | ||
315 | freeze_count++; | |
316 | ||
cb323159 | 317 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process"); |
d9a64523 A |
318 | } |
319 | ||
f427ee49 A |
320 | static void |
321 | skip_if_freezer_is_disabled() | |
322 | { | |
323 | int freeze_enabled; | |
324 | size_t length = sizeof(freeze_enabled); | |
325 | ||
326 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0), | |
327 | "failed to query vm.freeze_enabled"); | |
328 | if (!freeze_enabled) { | |
329 | /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */ | |
330 | T_SKIP("Freeze has been disabled. Skipping test."); | |
331 | } | |
332 | } | |
333 | ||
0a7de745 | 334 | void |
cb323159 | 335 | run_freezer_test(int num_pages) |
0a7de745 | 336 | { |
f427ee49 | 337 | int ret; |
d9a64523 A |
338 | char sz_str[50]; |
339 | char **launch_tool_args; | |
340 | char testpath[PATH_MAX]; | |
341 | uint32_t testpath_buf_size; | |
342 | dispatch_source_t ds_freeze, ds_proc; | |
0a7de745 | 343 | |
f427ee49 | 344 | skip_if_freezer_is_disabled(); |
d9a64523 A |
345 | |
346 | signal(SIGUSR1, SIG_IGN); | |
347 | ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); | |
348 | T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)"); | |
349 | ||
350 | dispatch_source_set_event_handler(ds_freeze, ^{ | |
351 | if (freeze_count < NUM_ITERATIONS) { | |
0a7de745 | 352 | freeze_helper_process(); |
d9a64523 | 353 | } else { |
cb323159 | 354 | kill(child_pid, SIGKILL); |
0a7de745 | 355 | dispatch_source_cancel(ds_freeze); |
d9a64523 A |
356 | } |
357 | }); | |
358 | dispatch_activate(ds_freeze); | |
359 | ||
360 | testpath_buf_size = sizeof(testpath); | |
361 | ret = _NSGetExecutablePath(testpath, &testpath_buf_size); | |
362 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath"); | |
363 | T_LOG("Executable path: %s", testpath); | |
364 | ||
cb323159 | 365 | sprintf(sz_str, "%d", num_pages); |
d9a64523 A |
366 | launch_tool_args = (char *[]){ |
367 | testpath, | |
368 | "-n", | |
369 | "allocate_pages", | |
370 | "--", | |
371 | sz_str, | |
372 | NULL | |
373 | }; | |
374 | ||
375 | /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */ | |
cb323159 | 376 | ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL); |
d9a64523 A |
377 | if (ret != 0) { |
378 | T_LOG("dt_launch tool returned %d with error code %d", ret, errno); | |
379 | } | |
cb323159 | 380 | T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool"); |
d9a64523 | 381 | |
cb323159 | 382 | ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue()); |
d9a64523 A |
383 | T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)"); |
384 | ||
385 | dispatch_source_set_event_handler(ds_proc, ^{ | |
386 | int status = 0, code = 0; | |
cb323159 A |
387 | pid_t rc = waitpid(child_pid, &status, 0); |
388 | T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid"); | |
d9a64523 A |
389 | code = WEXITSTATUS(status); |
390 | ||
391 | if (code == 0) { | |
0a7de745 | 392 | T_END; |
d9a64523 | 393 | } else if (code > 0 && code < EXIT_CODE_MAX) { |
0a7de745 | 394 | T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]); |
d9a64523 | 395 | } else { |
0a7de745 | 396 | T_ASSERT_FAIL("Child exited with unknown exit code %d", code); |
d9a64523 A |
397 | } |
398 | }); | |
399 | dispatch_activate(ds_proc); | |
400 | ||
cb323159 | 401 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process"); |
d9a64523 A |
402 | dispatch_main(); |
403 | } | |
404 | ||
cb323159 A |
405 | static void |
406 | allocate_pages(int num_pages) | |
407 | { | |
408 | int i, j, vmpgsize; | |
d9a64523 | 409 | char val; |
cb323159 | 410 | __block int num_iter = 0; |
d9a64523 A |
411 | __block char **buf; |
412 | dispatch_source_t ds_signal; | |
cb323159 A |
413 | vmpgsize = get_vmpage_size(); |
414 | if (num_pages < 1) { | |
415 | printf("Invalid number of pages to allocate: %d\n", num_pages); | |
416 | exit(INVALID_ALLOCATE_PAGES_ARGUMENTS); | |
d9a64523 A |
417 | } |
418 | ||
d9a64523 A |
419 | buf = (char**)malloc(sizeof(char*) * (size_t)num_pages); |
420 | ||
421 | /* Gives us the compression ratio we see in the typical case (~2.7) */ | |
422 | for (j = 0; j < num_pages; j++) { | |
423 | buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char)); | |
424 | val = 0; | |
425 | for (i = 0; i < vmpgsize; i += 16) { | |
426 | memset(&buf[j][i], val, 16); | |
427 | if (i < 3400 * (vmpgsize / 4096)) { | |
428 | val++; | |
429 | } | |
430 | } | |
431 | } | |
432 | ||
433 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{ | |
434 | /* Signal to the parent that we're done allocating and it's ok to freeze us */ | |
0a7de745 | 435 | printf("[%d] Sending initial signal to parent to begin freezing\n", getpid()); |
d9a64523 | 436 | if (kill(getppid(), SIGUSR1) != 0) { |
0a7de745 | 437 | exit(INITIAL_SIGNAL_TO_PARENT_FAILED); |
d9a64523 A |
438 | } |
439 | }); | |
440 | ||
441 | signal(SIGUSR1, SIG_IGN); | |
442 | ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); | |
443 | if (ds_signal == NULL) { | |
444 | exit(DISPATCH_SOURCE_CREATE_FAILED); | |
445 | } | |
446 | ||
447 | dispatch_source_set_event_handler(ds_signal, ^{ | |
448 | int current_state, new_state; | |
449 | volatile int tmp; | |
450 | ||
451 | /* Make sure all the pages are accessed before trying to freeze again */ | |
452 | for (int x = 0; x < num_pages; x++) { | |
0a7de745 | 453 | tmp = buf[x][0]; |
d9a64523 A |
454 | } |
455 | ||
456 | current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0); | |
cb323159 A |
457 | /* Sysprocs start off as unfreezable. Verify that first. */ |
458 | if (num_iter == 0 && current_state != 0) { | |
459 | exit(IS_FREEZABLE_NOT_AS_EXPECTED); | |
460 | } | |
d9a64523 A |
461 | |
462 | /* Toggle freezable state */ | |
463 | new_state = (current_state) ? 0: 1; | |
0a7de745 A |
464 | printf("[%d] Changing state from %s to %s\n", getpid(), |
465 | (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable"); | |
d9a64523 | 466 | if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) { |
0a7de745 | 467 | exit(MEMORYSTATUS_CONTROL_FAILED); |
d9a64523 A |
468 | } |
469 | ||
470 | /* Verify that the state has been set correctly */ | |
471 | current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0); | |
472 | if (new_state != current_state) { | |
0a7de745 | 473 | exit(IS_FREEZABLE_NOT_AS_EXPECTED); |
d9a64523 A |
474 | } |
475 | num_iter++; | |
476 | ||
477 | if (kill(getppid(), SIGUSR1) != 0) { | |
0a7de745 | 478 | exit(SIGNAL_TO_PARENT_FAILED); |
d9a64523 A |
479 | } |
480 | }); | |
481 | dispatch_activate(ds_signal); | |
f427ee49 | 482 | move_to_idle_band(getpid()); |
d9a64523 A |
483 | |
484 | dispatch_main(); | |
485 | } | |
486 | ||
cb323159 A |
487 | T_HELPER_DECL(allocate_pages, |
488 | "allocates pages to freeze", | |
489 | T_META_ASROOT(true)) { | |
490 | if (argc < 1) { | |
491 | exit(TOO_FEW_ARGUMENTS); | |
492 | } | |
493 | ||
494 | int num_pages = atoi(argv[0]); | |
495 | allocate_pages(num_pages); | |
496 | } | |
497 | ||
498 | T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) { | |
499 | run_freezer_test( | |
500 | (MEM_SIZE_MB << 20) / get_vmpage_size()); | |
501 | } | |
502 | ||
503 | static int old_freeze_pages_max = 0; | |
504 | static void | |
505 | reset_freeze_pages_max() | |
506 | { | |
507 | if (old_freeze_pages_max != 0) { | |
508 | sysctl_freeze_pages_max(&old_freeze_pages_max); | |
509 | } | |
510 | } | |
511 | ||
512 | int | |
513 | sysctl_freeze_pages_max(int* new_value) | |
514 | { | |
515 | static int set_end_handler = false; | |
516 | int freeze_pages_max, ret; | |
517 | size_t size = sizeof(freeze_pages_max); | |
518 | ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size); | |
519 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max"); | |
520 | if (!set_end_handler) { | |
521 | // Save the original value and instruct darwintest to restore it after the test completes | |
522 | old_freeze_pages_max = freeze_pages_max; | |
523 | T_ATEND(reset_freeze_pages_max); | |
524 | set_end_handler = true; | |
525 | } | |
526 | return old_freeze_pages_max; | |
527 | } | |
528 | ||
529 | T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) { | |
530 | int freeze_pages_max = FREEZE_PAGES_MAX; | |
531 | sysctl_freeze_pages_max(&freeze_pages_max); | |
532 | run_freezer_test(FREEZE_PAGES_MAX * 2); | |
533 | } | |
534 | ||
535 | T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) { | |
536 | kern_return_t kern_ret; | |
537 | /* Set the process to freezable */ | |
538 | kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0); | |
539 | T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable"); | |
540 | /* Signal to our parent that we can be frozen */ | |
541 | if (kill(getppid(), SIGUSR1) != 0) { | |
542 | T_LOG("Unable to signal to parent process!"); | |
543 | exit(1); | |
544 | } | |
545 | while (1) { | |
546 | ; | |
547 | } | |
548 | } | |
549 | ||
550 | /* Launches the frozen_background helper as a managed process. */ | |
551 | static pid_t | |
2a1bd2d3 | 552 | launch_background_helper(const char* variant) |
cb323159 A |
553 | { |
554 | pid_t pid; | |
555 | char **launch_tool_args; | |
556 | char testpath[PATH_MAX]; | |
c3c9b80d | 557 | char *variant_cpy = strdup(variant); |
cb323159 A |
558 | uint32_t testpath_buf_size; |
559 | int ret; | |
560 | ||
561 | testpath_buf_size = sizeof(testpath); | |
562 | ret = _NSGetExecutablePath(testpath, &testpath_buf_size); | |
563 | printf("Launching %s\n", testpath); | |
564 | launch_tool_args = (char *[]){ | |
565 | testpath, | |
566 | "-n", | |
c3c9b80d | 567 | variant_cpy, |
cb323159 A |
568 | NULL |
569 | }; | |
570 | ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL); | |
571 | if (ret != 0) { | |
572 | T_LOG("dt_launch tool returned %d with error code %d", ret, errno); | |
573 | } | |
574 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dt_launch_tool"); | |
575 | /* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */ | |
576 | ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, pid, 1, NULL, 0); | |
577 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control"); | |
c3c9b80d | 578 | free(variant_cpy); |
cb323159 A |
579 | return pid; |
580 | } | |
581 | ||
582 | static void | |
583 | freeze_process(pid_t pid) | |
584 | { | |
585 | int ret, freeze_enabled, errno_freeze_sysctl; | |
586 | size_t length; | |
587 | T_LOG("Freezing pid %d", pid); | |
588 | ||
589 | ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid)); | |
590 | errno_freeze_sysctl = errno; | |
591 | length = sizeof(freeze_enabled); | |
592 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0), | |
593 | "failed to query vm.freeze_enabled"); | |
594 | if (freeze_enabled) { | |
595 | errno = errno_freeze_sysctl; | |
596 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed"); | |
597 | } else { | |
598 | /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */ | |
599 | T_LOG("Freeze has been disabled. Terminating early."); | |
600 | T_END; | |
601 | } | |
602 | } | |
603 | ||
604 | static void | |
605 | memorystatus_assertion_test_demote_frozen() | |
606 | { | |
cb323159 A |
607 | /* |
608 | * Test that if we assert a priority on a process, freeze it, and then demote all frozen processes, it does not get demoted below the asserted priority. | |
609 | * Then remove thee assertion, and ensure it gets demoted properly. | |
610 | */ | |
611 | /* these values will remain fixed during testing */ | |
612 | int active_limit_mb = 15; /* arbitrary */ | |
613 | int inactive_limit_mb = 7; /* arbitrary */ | |
c3c9b80d | 614 | __block int demote_value = 1; |
cb323159 A |
615 | /* Launch the child process, and elevate its priority */ |
616 | int requestedpriority; | |
617 | dispatch_source_t ds_signal, ds_exit; | |
618 | requestedpriority = JETSAM_PRIORITY_UI_SUPPORT; | |
619 | ||
620 | /* Wait for the child process to tell us that it's ready, and then freeze it */ | |
621 | signal(SIGUSR1, SIG_IGN); | |
622 | ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); | |
623 | T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create"); | |
624 | dispatch_source_set_event_handler(ds_signal, ^{ | |
625 | int sysctl_ret; | |
626 | /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */ | |
627 | freeze_process(child_pid); | |
628 | /* Agressive demotion */ | |
ea3f0419 A |
629 | sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value)); |
630 | T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded"); | |
cb323159 A |
631 | /* Check */ |
632 | (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set"); | |
633 | T_LOG("Relinquishing our assertion."); | |
634 | /* Relinquish our assertion, and check that it gets demoted. */ | |
635 | relinquish_assertion_priority(child_pid, 0x0); | |
636 | (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished."); | |
637 | /* Kill the child */ | |
ea3f0419 | 638 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process"); |
cb323159 A |
639 | T_END; |
640 | }); | |
641 | ||
642 | /* Launch the child process and set the initial properties on it. */ | |
2a1bd2d3 | 643 | child_pid = launch_background_helper("frozen_background"); |
cb323159 A |
644 | set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false); |
645 | set_assertion_priority(child_pid, requestedpriority, 0x0); | |
646 | (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set"); | |
647 | /* Listen for exit. */ | |
648 | ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue()); | |
649 | dispatch_source_set_event_handler(ds_exit, ^{ | |
650 | int status = 0, code = 0; | |
651 | pid_t rc = waitpid(child_pid, &status, 0); | |
652 | T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid"); | |
653 | code = WEXITSTATUS(status); | |
654 | T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly"); | |
655 | T_END; | |
656 | }); | |
657 | ||
658 | dispatch_activate(ds_exit); | |
659 | dispatch_activate(ds_signal); | |
660 | dispatch_main(); | |
661 | } | |
662 | ||
663 | T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) { | |
664 | memorystatus_assertion_test_demote_frozen(); | |
d9a64523 | 665 | } |
ea3f0419 A |
666 | |
667 | T_DECL(budget_replenishment, "budget replenishes properly") { | |
668 | size_t length; | |
669 | int ret; | |
670 | static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours | |
671 | unsigned int memorystatus_freeze_daily_mb_max, memorystatus_freeze_daily_pages_max; | |
672 | static unsigned int kFixedPointFactor = 100; | |
673 | static unsigned int kNumSecondsInDay = 60 * 60 * 24; | |
674 | unsigned int new_budget, expected_new_budget_pages; | |
675 | size_t new_budget_ln; | |
676 | unsigned int page_size = (unsigned int) get_vmpage_size(); | |
677 | ||
678 | /* | |
679 | * Calculate a new budget as if the previous interval expired kTestIntervalSecs | |
680 | * ago and we used up its entire budget. | |
681 | */ | |
682 | length = sizeof(kTestIntervalSecs); | |
683 | new_budget_ln = sizeof(new_budget); | |
684 | ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length); | |
bca245ac | 685 | T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget"); |
ea3f0419 A |
686 | |
687 | // Grab the daily budget. | |
688 | length = sizeof(memorystatus_freeze_daily_mb_max); | |
689 | ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0); | |
bca245ac | 690 | T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max"); |
ea3f0419 | 691 | |
bca245ac A |
692 | memorystatus_freeze_daily_pages_max = memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / page_size; |
693 | T_LOG("memorystatus_freeze_daily_mb_max %u", memorystatus_freeze_daily_mb_max); | |
694 | T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max); | |
695 | T_LOG("page_size %u", page_size); | |
ea3f0419 A |
696 | |
697 | /* | |
698 | * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay | |
699 | * seconds of budget. | |
700 | */ | |
701 | expected_new_budget_pages = memorystatus_freeze_daily_pages_max; | |
bca245ac A |
702 | T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages); |
703 | T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change"); | |
ea3f0419 A |
704 | expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay) |
705 | * memorystatus_freeze_daily_pages_max) / kFixedPointFactor; | |
bca245ac A |
706 | T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages); |
707 | T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max); | |
ea3f0419 A |
708 | |
709 | T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly."); | |
710 | } | |
f427ee49 A |
711 | |
712 | ||
713 | static bool | |
714 | is_proc_in_frozen_list(pid_t pid, char* name, size_t name_len) | |
715 | { | |
716 | int bytes_written; | |
717 | bool found = false; | |
718 | global_frozen_procs_t *frozen_procs = malloc(sizeof(global_frozen_procs_t)); | |
719 | T_QUIET; T_ASSERT_NOTNULL(frozen_procs, "malloc"); | |
720 | ||
721 | bytes_written = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_GET_PROCS, frozen_procs, sizeof(global_frozen_procs_t)); | |
722 | T_QUIET; T_ASSERT_LE((size_t) bytes_written, sizeof(global_frozen_procs_t), "Didn't overflow buffer"); | |
723 | T_QUIET; T_ASSERT_GT(bytes_written, 0, "Wrote someting"); | |
724 | ||
725 | for (size_t i = 0; i < frozen_procs->gfp_num_frozen; i++) { | |
726 | if (frozen_procs->gfp_procs[i].fp_pid == pid) { | |
727 | found = true; | |
728 | strlcpy(name, frozen_procs->gfp_procs[i].fp_name, name_len); | |
729 | } | |
730 | } | |
731 | return found; | |
732 | } | |
733 | ||
734 | static void | |
c3c9b80d | 735 | unset_testing_pid(void) |
f427ee49 A |
736 | { |
737 | int ret; | |
c3c9b80d | 738 | ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_UNSET_TESTING_PID, NULL, 0); |
f427ee49 A |
739 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Drop ownership of jetsam snapshot"); |
740 | } | |
741 | ||
742 | static void | |
c3c9b80d | 743 | set_testing_pid(void) |
f427ee49 A |
744 | { |
745 | int ret; | |
c3c9b80d | 746 | ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_SET_TESTING_PID, NULL, 0); |
f427ee49 | 747 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Take ownership of jetsam snapshot"); |
c3c9b80d | 748 | T_ATEND(unset_testing_pid); |
f427ee49 A |
749 | } |
750 | ||
751 | /* | |
752 | * Retrieve a jetsam snapshot. | |
753 | * | |
754 | * return: | |
755 | * pointer to snapshot. | |
756 | * | |
757 | * Caller is responsible for freeing snapshot. | |
758 | */ | |
759 | static | |
760 | memorystatus_jetsam_snapshot_t * | |
761 | get_jetsam_snapshot(uint32_t flags, bool empty_allowed) | |
762 | { | |
763 | memorystatus_jetsam_snapshot_t * snapshot = NULL; | |
764 | int ret; | |
765 | uint32_t size; | |
766 | ||
767 | ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, NULL, 0); | |
768 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Get jetsam snapshot size"); | |
769 | size = (uint32_t) ret; | |
770 | if (size == 0 && empty_allowed) { | |
771 | return snapshot; | |
772 | } | |
773 | ||
774 | snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size); | |
775 | T_QUIET; T_ASSERT_NOTNULL(snapshot, "Allocate snapshot of size %d", size); | |
776 | ||
777 | ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, snapshot, size); | |
778 | T_QUIET; T_ASSERT_GT(size, 0, "Get jetsam snapshot"); | |
779 | ||
780 | if (((size - sizeof(memorystatus_jetsam_snapshot_t)) / sizeof(memorystatus_jetsam_snapshot_entry_t)) != snapshot->entry_count) { | |
781 | T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size, | |
782 | sizeof(memorystatus_jetsam_snapshot_t), snapshot->entry_count, sizeof(memorystatus_jetsam_snapshot_entry_t), | |
783 | sizeof(memorystatus_jetsam_snapshot_t) + (snapshot->entry_count * sizeof(memorystatus_jetsam_snapshot_entry_t))); | |
784 | if (snapshot) { | |
785 | free(snapshot); | |
786 | } | |
787 | } | |
788 | ||
789 | return snapshot; | |
790 | } | |
791 | ||
792 | /* | |
793 | * Look for the given pid in the snapshot. | |
794 | * | |
795 | * return: | |
796 | * pointer to pid's entry or NULL if pid is not found. | |
797 | * | |
798 | * Caller has ownership of snapshot before and after call. | |
799 | */ | |
800 | static | |
801 | memorystatus_jetsam_snapshot_entry_t * | |
802 | get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t *snapshot, pid_t pid) | |
803 | { | |
804 | T_QUIET; T_ASSERT_NOTNULL(snapshot, "Got snapshot"); | |
805 | for (size_t i = 0; i < snapshot->entry_count; i++) { | |
806 | memorystatus_jetsam_snapshot_entry_t *curr = &(snapshot->entries[i]); | |
807 | if (curr->pid == pid) { | |
808 | return curr; | |
809 | } | |
810 | } | |
811 | ||
812 | return NULL; | |
813 | } | |
814 | ||
c3c9b80d A |
815 | static dispatch_source_t |
816 | run_block_after_signal(int sig, dispatch_block_t block) | |
817 | { | |
818 | dispatch_source_t ds_signal; | |
819 | signal(sig, SIG_IGN); | |
820 | ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) sig, 0, dispatch_get_main_queue()); | |
821 | T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create"); | |
822 | dispatch_source_set_event_handler(ds_signal, block); | |
823 | return ds_signal; | |
824 | } | |
825 | ||
f427ee49 A |
826 | /* |
827 | * Launches the child & runs the given block after the child signals. | |
828 | * If exit_with_child is true, the test will exit when the child exits. | |
829 | */ | |
830 | static void | |
2a1bd2d3 | 831 | test_after_background_helper_launches(bool exit_with_child, const char* variant, dispatch_block_t test_block) |
f427ee49 A |
832 | { |
833 | dispatch_source_t ds_signal, ds_exit; | |
834 | ||
c3c9b80d | 835 | ds_signal = run_block_after_signal(SIGUSR1, test_block); |
f427ee49 | 836 | /* Launch the child process. */ |
2a1bd2d3 | 837 | child_pid = launch_background_helper(variant); |
f427ee49 A |
838 | /* Listen for exit. */ |
839 | if (exit_with_child) { | |
840 | ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue()); | |
841 | dispatch_source_set_event_handler(ds_exit, ^{ | |
842 | int status = 0, code = 0; | |
843 | pid_t rc = waitpid(child_pid, &status, 0); | |
844 | T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid"); | |
845 | code = WEXITSTATUS(status); | |
2a1bd2d3 A |
846 | if (code != 0) { |
847 | T_LOG("Child exited with error: %s", exit_codes_str[code]); | |
848 | } | |
f427ee49 A |
849 | T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly"); |
850 | T_END; | |
851 | }); | |
852 | ||
853 | dispatch_activate(ds_exit); | |
854 | } | |
855 | dispatch_activate(ds_signal); | |
f427ee49 A |
856 | } |
857 | ||
858 | T_DECL(get_frozen_procs, "List processes in the freezer") { | |
859 | skip_if_freezer_is_disabled(); | |
860 | ||
2a1bd2d3 | 861 | test_after_background_helper_launches(true, "frozen_background", ^{ |
f427ee49 A |
862 | proc_name_t name; |
863 | /* Place the child in the idle band so that it gets elevated like a typical app. */ | |
864 | move_to_idle_band(child_pid); | |
865 | /* Freeze the process, and check that it's in the list of frozen processes. */ | |
866 | freeze_process(child_pid); | |
867 | /* Check */ | |
868 | T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list"); | |
869 | T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name"); | |
870 | /* Kill the child */ | |
871 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process"); | |
872 | T_END; | |
873 | }); | |
c3c9b80d | 874 | dispatch_main(); |
f427ee49 A |
875 | } |
876 | ||
877 | T_DECL(frozen_to_swap_accounting, "jetsam snapshot has frozen_to_swap accounting") { | |
878 | static const size_t kSnapshotSleepDelay = 5; | |
879 | static const size_t kFreezeToDiskMaxDelay = 60; | |
880 | ||
881 | skip_if_freezer_is_disabled(); | |
882 | ||
2a1bd2d3 | 883 | test_after_background_helper_launches(true, "frozen_background", ^{ |
f427ee49 A |
884 | memorystatus_jetsam_snapshot_t *snapshot = NULL; |
885 | memorystatus_jetsam_snapshot_entry_t *child_entry = NULL; | |
886 | /* Place the child in the idle band so that it gets elevated like a typical app. */ | |
887 | move_to_idle_band(child_pid); | |
888 | freeze_process(child_pid); | |
889 | /* | |
890 | * Wait until the child's pages get paged out to disk. | |
891 | * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds, | |
892 | * something is either wrong with the compactor or the accounting. | |
893 | */ | |
894 | for (size_t i = 0; i < kFreezeToDiskMaxDelay / kSnapshotSleepDelay; i++) { | |
895 | snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false); | |
896 | child_entry = get_jetsam_snapshot_entry(snapshot, child_pid); | |
897 | T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot"); | |
898 | if (child_entry->jse_frozen_to_swap_pages > 0) { | |
899 | break; | |
900 | } | |
901 | free(snapshot); | |
902 | sleep(kSnapshotSleepDelay); | |
903 | } | |
904 | T_QUIET; T_ASSERT_GT(child_entry->jse_frozen_to_swap_pages, 0ULL, "child has some pages in swap"); | |
905 | free(snapshot); | |
906 | /* Kill the child */ | |
907 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process"); | |
908 | T_END; | |
909 | }); | |
c3c9b80d | 910 | dispatch_main(); |
f427ee49 A |
911 | } |
912 | ||
913 | T_DECL(freezer_snapshot, "App kills are recorded in the freezer snapshot") { | |
914 | /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */ | |
c3c9b80d | 915 | set_testing_pid(); |
f427ee49 | 916 | |
2a1bd2d3 | 917 | test_after_background_helper_launches(false, "frozen_background", ^{ |
f427ee49 A |
918 | int ret; |
919 | memorystatus_jetsam_snapshot_t *snapshot = NULL; | |
920 | memorystatus_jetsam_snapshot_entry_t *child_entry = NULL; | |
921 | ||
922 | ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0); | |
923 | T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child"); | |
924 | ||
925 | snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false); | |
926 | T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot"); | |
927 | child_entry = get_jetsam_snapshot_entry(snapshot, child_pid); | |
928 | T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot"); | |
929 | T_QUIET; T_ASSERT_EQ(child_entry->killed, (unsigned long long) JETSAM_REASON_GENERIC, "Child entry was killed"); | |
930 | ||
931 | free(snapshot); | |
932 | T_END; | |
933 | }); | |
c3c9b80d | 934 | dispatch_main(); |
f427ee49 A |
935 | } |
936 | ||
937 | T_DECL(freezer_snapshot_consume, "Freezer snapshot is consumed on read") { | |
938 | /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */ | |
c3c9b80d | 939 | set_testing_pid(); |
f427ee49 | 940 | |
2a1bd2d3 | 941 | test_after_background_helper_launches(false, "frozen_background", ^{ |
f427ee49 A |
942 | int ret; |
943 | memorystatus_jetsam_snapshot_t *snapshot = NULL; | |
944 | memorystatus_jetsam_snapshot_entry_t *child_entry = NULL; | |
945 | ||
946 | ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0); | |
947 | T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child"); | |
948 | ||
949 | snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false); | |
950 | T_ASSERT_NOTNULL(snapshot, "Got first freezer snapshot"); | |
951 | child_entry = get_jetsam_snapshot_entry(snapshot, child_pid); | |
952 | T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in first freezer snapshot"); | |
953 | ||
954 | snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, true); | |
955 | if (snapshot != NULL) { | |
956 | child_entry = get_jetsam_snapshot_entry(snapshot, child_pid); | |
957 | T_QUIET; T_ASSERT_NULL(child_entry, "Child is not in second freezer snapshot"); | |
958 | } | |
959 | ||
960 | free(snapshot); | |
961 | T_END; | |
962 | }); | |
c3c9b80d | 963 | dispatch_main(); |
f427ee49 A |
964 | } |
965 | ||
966 | T_DECL(freezer_snapshot_frozen_state, "Frozen state is recorded in freezer snapshot") { | |
967 | skip_if_freezer_is_disabled(); | |
968 | /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */ | |
c3c9b80d | 969 | set_testing_pid(); |
f427ee49 | 970 | |
2a1bd2d3 | 971 | test_after_background_helper_launches(false, "frozen_background", ^{ |
f427ee49 A |
972 | int ret; |
973 | memorystatus_jetsam_snapshot_t *snapshot = NULL; | |
974 | memorystatus_jetsam_snapshot_entry_t *child_entry = NULL; | |
975 | ||
976 | move_to_idle_band(child_pid); | |
977 | freeze_process(child_pid); | |
978 | ||
979 | ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0); | |
980 | T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child"); | |
981 | ||
982 | snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false); | |
983 | T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot"); | |
984 | child_entry = get_jetsam_snapshot_entry(snapshot, child_pid); | |
985 | T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot"); | |
986 | T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is set"); | |
987 | ||
988 | free(snapshot); | |
989 | T_END; | |
990 | }); | |
c3c9b80d | 991 | dispatch_main(); |
f427ee49 A |
992 | } |
993 | ||
994 | T_DECL(freezer_snapshot_thaw_state, "Thaw count is recorded in freezer snapshot") { | |
995 | skip_if_freezer_is_disabled(); | |
996 | /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */ | |
c3c9b80d | 997 | set_testing_pid(); |
f427ee49 | 998 | |
2a1bd2d3 | 999 | test_after_background_helper_launches(false, "frozen_background", ^{ |
f427ee49 A |
1000 | int ret; |
1001 | memorystatus_jetsam_snapshot_t *snapshot = NULL; | |
1002 | memorystatus_jetsam_snapshot_entry_t *child_entry = NULL; | |
1003 | ||
1004 | move_to_idle_band(child_pid); | |
1005 | ret = pid_suspend(child_pid); | |
1006 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended"); | |
1007 | freeze_process(child_pid); | |
1008 | ret = pid_resume(child_pid); | |
1009 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze"); | |
1010 | ||
1011 | ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0); | |
1012 | T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child"); | |
1013 | ||
1014 | snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false); | |
1015 | T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot"); | |
1016 | child_entry = get_jetsam_snapshot_entry(snapshot, child_pid); | |
1017 | T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot"); | |
1018 | T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is still set after thaw"); | |
1019 | T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusWasThawed, "Child entry was thawed"); | |
1020 | T_QUIET; T_ASSERT_EQ(child_entry->jse_thaw_count, 1ULL, "Child entry's thaw count was incremented"); | |
1021 | ||
1022 | free(snapshot); | |
1023 | T_END; | |
1024 | }); | |
1025 | } | |
2a1bd2d3 A |
1026 | |
1027 | T_HELPER_DECL(check_frozen, "Check frozen state", T_META_ASROOT(true)) { | |
1028 | int kern_ret; | |
1029 | dispatch_source_t ds_signal; | |
1030 | __block int is_frozen; | |
1031 | /* Set the process to freezable */ | |
1032 | kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0); | |
1033 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kern_ret, "set process is freezable"); | |
2a1bd2d3 A |
1034 | |
1035 | /* We should not be frozen yet. */ | |
1036 | is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0); | |
1037 | if (is_frozen == -1) { | |
1038 | T_LOG("memorystatus_control error: %s", strerror(errno)); | |
1039 | exit(MEMORYSTATUS_CONTROL_ERROR); | |
1040 | } | |
1041 | if (is_frozen) { | |
1042 | exit(FROZEN_BIT_SET); | |
1043 | } | |
1044 | ||
2a1bd2d3 A |
1045 | ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); |
1046 | if (ds_signal == NULL) { | |
1047 | exit(DISPATCH_SOURCE_CREATE_FAILED); | |
1048 | } | |
1049 | ||
1050 | dispatch_source_set_event_handler(ds_signal, ^{ | |
1051 | /* We should now be frozen. */ | |
1052 | is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0); | |
1053 | if (is_frozen == -1) { | |
1054 | T_LOG("memorystatus_control error: %s", strerror(errno)); | |
1055 | exit(MEMORYSTATUS_CONTROL_ERROR); | |
1056 | } | |
1057 | if (!is_frozen) { | |
1058 | exit(FROZEN_BIT_NOT_SET); | |
1059 | } | |
1060 | exit(SUCCESS); | |
1061 | }); | |
1062 | dispatch_activate(ds_signal); | |
1063 | ||
c3c9b80d A |
1064 | sig_t sig_ret = signal(SIGUSR1, SIG_IGN); |
1065 | T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)"); | |
1066 | ||
1067 | /* Signal to our parent that we can be frozen */ | |
1068 | if (kill(getppid(), SIGUSR1) != 0) { | |
1069 | T_LOG("Unable to signal to parent process!"); | |
1070 | exit(SIGNAL_TO_PARENT_FAILED); | |
1071 | } | |
1072 | ||
2a1bd2d3 A |
1073 | dispatch_main(); |
1074 | } | |
1075 | ||
1076 | T_DECL(memorystatus_get_process_is_frozen, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state") { | |
1077 | skip_if_freezer_is_disabled(); | |
1078 | ||
1079 | test_after_background_helper_launches(true, "check_frozen", ^{ | |
1080 | int ret; | |
1081 | /* Freeze the child, resume it, and signal it to check its state */ | |
1082 | move_to_idle_band(child_pid); | |
1083 | ret = pid_suspend(child_pid); | |
1084 | T_ASSERT_POSIX_SUCCESS(ret, "child suspended"); | |
1085 | freeze_process(child_pid); | |
1086 | ret = pid_resume(child_pid); | |
1087 | T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze"); | |
1088 | ||
1089 | kill(child_pid, SIGUSR1); | |
1090 | /* The child will checks its own frozen state & exit. */ | |
1091 | }); | |
c3c9b80d A |
1092 | dispatch_main(); |
1093 | } | |
1094 | ||
1095 | static unsigned int freeze_pages_min_old; | |
1096 | static int throttle_enabled_old; | |
1097 | static void cleanup_memorystatus_freeze_top_process() { | |
1098 | sysctlbyname("kern.memorystatus_freeze_pages_min", NULL, NULL, &freeze_pages_min_old, sizeof(freeze_pages_min_old)); | |
1099 | sysctlbyname("kern.memorystatus_freeze_throttle_enabled", NULL, NULL, &throttle_enabled_old, sizeof(throttle_enabled_old)); | |
1100 | } | |
1101 | ||
1102 | #define P_MEMSTAT_FROZEN 0x00000002 | |
1103 | T_DECL(memorystatus_freeze_top_process, "memorystatus_freeze_top_process chooses the correct process", | |
1104 | T_META_ASROOT(true), | |
1105 | T_META_REQUIRES_SYSCTL_EQ("kern.development", 1), | |
1106 | T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) { | |
1107 | int32_t memorystatus_freeze_band = 0; | |
1108 | size_t memorystatus_freeze_band_size = sizeof(memorystatus_freeze_band); | |
1109 | size_t freeze_pages_min_size = sizeof(freeze_pages_min_old); | |
1110 | unsigned int freeze_pages_min_new = 0; | |
1111 | size_t throttle_enabled_old_size = sizeof(throttle_enabled_old); | |
1112 | int throttle_enabled_new = 1; | |
1113 | __block errno_t ret; | |
1114 | __block int maxproc; | |
1115 | size_t maxproc_size = sizeof(maxproc); | |
1116 | ||
1117 | ret = sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, NULL, 0); | |
1118 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.maxproc"); | |
1119 | sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band, &memorystatus_freeze_band_size, NULL, 0); | |
1120 | ||
1121 | /* Set min pages to 0 and disable the budget to ensure we can always freeze the child. */ | |
1122 | ret = sysctlbyname("kern.memorystatus_freeze_pages_min", &freeze_pages_min_old, &freeze_pages_min_size, &freeze_pages_min_new, sizeof(freeze_pages_min_new)); | |
1123 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_pages_min"); | |
1124 | ret = sysctlbyname("kern.memorystatus_freeze_throttle_enabled", &throttle_enabled_old, &throttle_enabled_old_size, &throttle_enabled_new, sizeof(throttle_enabled_new)); | |
1125 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_throttle_enabled"); | |
1126 | T_ATEND(cleanup_memorystatus_freeze_top_process); | |
1127 | /* Take ownership of the freezer probabilities for the duration of the test so that we don't race with dasd. */ | |
1128 | set_testing_pid(); | |
1129 | test_after_background_helper_launches(true, "frozen_background", ^{ | |
1130 | int32_t child_band = JETSAM_PRIORITY_DEFAULT; | |
1131 | /* Place the child in the idle band so that it gets elevated like a typical app. */ | |
1132 | move_to_idle_band(child_pid); | |
1133 | ret = pid_suspend(child_pid); | |
1134 | T_ASSERT_POSIX_SUCCESS(ret, "child suspended"); | |
1135 | ||
1136 | size_t buffer_len = sizeof(memorystatus_properties_entry_v1_t) * (size_t) maxproc; | |
1137 | memorystatus_properties_entry_v1_t *properties_list = malloc(buffer_len); | |
1138 | T_QUIET; T_ASSERT_NOTNULL(properties_list, "malloc properties array"); | |
1139 | size_t properties_list_len = 0; | |
1140 | /* The child needs to age down into the idle band before it's eligible to be frozen. */ | |
1141 | T_LOG("Waiting for child to age into the idle band."); | |
1142 | while (child_band != JETSAM_PRIORITY_IDLE) { | |
1143 | memset(properties_list, 0, buffer_len); | |
1144 | properties_list_len = 0; | |
1145 | memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false); | |
1146 | ||
1147 | bool found = false; | |
1148 | for (size_t i = 0; i < snapshot->entry_count; i++) { | |
1149 | memorystatus_jetsam_snapshot_entry_t *snapshot_entry = &snapshot->entries[i]; | |
1150 | if (snapshot_entry->priority <= memorystatus_freeze_band && !snapshot_entry->killed) { | |
1151 | pid_t pid = snapshot_entry->pid; | |
1152 | memorystatus_properties_entry_v1_t *property_entry = &properties_list[properties_list_len++]; | |
1153 | property_entry->version = 1; | |
1154 | property_entry->pid = pid; | |
1155 | if (pid == child_pid) { | |
1156 | found = true; | |
1157 | property_entry->use_probability = 1; | |
1158 | child_band = snapshot_entry->priority; | |
1159 | } else { | |
1160 | property_entry->use_probability = 0; | |
1161 | } | |
1162 | strncpy(property_entry->proc_name, snapshot_entry->name, MAXCOMLEN); | |
1163 | property_entry->proc_name[MAXCOMLEN] = '\0'; | |
1164 | } | |
1165 | } | |
1166 | T_QUIET; T_ASSERT_TRUE(found, "Child is in on demand snapshot"); | |
1167 | free(snapshot); | |
1168 | } | |
1169 | ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY, properties_list, sizeof(memorystatus_properties_entry_v1_t) * properties_list_len); | |
1170 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY"); | |
1171 | free(properties_list); | |
1172 | int val = 1; | |
1173 | ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val)); | |
1174 | T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process"); | |
1175 | /* Verify that the process was frozen. */ | |
1176 | memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false); | |
1177 | memorystatus_jetsam_snapshot_entry_t *entry = get_jetsam_snapshot_entry(snapshot, child_pid); | |
1178 | T_ASSERT_NOTNULL(entry, "child is in snapshot"); | |
1179 | if (!(entry->state & P_MEMSTAT_FROZEN)) { | |
1180 | T_LOG("Not frozen. Skip reason: %d", entry->jse_freeze_skip_reason); | |
1181 | } | |
1182 | T_ASSERT_TRUE(entry->state & P_MEMSTAT_FROZEN, "child is frozen"); | |
1183 | free(snapshot); | |
1184 | ret = pid_resume(child_pid); | |
1185 | T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze"); | |
1186 | ||
1187 | /* Kill the child */ | |
1188 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process"); | |
1189 | T_END; | |
1190 | }); | |
1191 | dispatch_main(); | |
1192 | } | |
1193 | ||
1194 | static int | |
1195 | memorystatus_freezer_thaw_percentage(void) | |
1196 | { | |
1197 | int val; | |
1198 | size_t size = sizeof(val); | |
1199 | int ret = sysctlbyname("kern.memorystatus_freezer_thaw_percentage", &val, &size, NULL, 0); | |
1200 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freezer_thaw_percentage"); | |
1201 | return val; | |
1202 | } | |
1203 | ||
1204 | static void | |
1205 | reset_interval(void) | |
1206 | { | |
1207 | uint32_t freeze_daily_budget_mb = 0; | |
1208 | size_t size = sizeof(freeze_daily_budget_mb); | |
1209 | int ret; | |
1210 | uint64_t new_budget; | |
1211 | ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &freeze_daily_budget_mb, &size, NULL, 0); | |
1212 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freeze_daily_mb_max"); | |
1213 | new_budget = (freeze_daily_budget_mb * (1UL << 20) / vm_page_size); | |
1214 | ret = sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL, NULL, &new_budget, sizeof(new_budget)); | |
1215 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to set kern.memorystatus_freeze_budget_pages_remaining"); | |
1216 | } | |
1217 | ||
1218 | static pid_t second_child; | |
1219 | static void | |
1220 | cleanup_memorystatus_freezer_thaw_percentage(void) | |
1221 | { | |
1222 | kill(second_child, SIGKILL); | |
1223 | } | |
1224 | ||
1225 | T_DECL(memorystatus_freezer_thaw_percentage, "memorystatus_freezer_thaw_percentage updates correctly", | |
1226 | T_META_ASROOT(true), | |
1227 | T_META_REQUIRES_SYSCTL_EQ("kern.development", 1), | |
1228 | T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) { | |
1229 | __block dispatch_source_t first_signal_block; | |
1230 | /* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */ | |
1231 | set_testing_pid(); | |
1232 | reset_interval(); | |
1233 | ||
1234 | /* Spawn one child that will remain frozen throughout the whole test & another that will be thawed. */ | |
1235 | first_signal_block = run_block_after_signal(SIGUSR1, ^{ | |
1236 | move_to_idle_band(second_child); | |
1237 | __block int ret = pid_suspend(second_child); | |
1238 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended"); | |
1239 | freeze_process(second_child); | |
1240 | T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage is still 0 after freeze"); | |
1241 | dispatch_source_cancel(first_signal_block); | |
1242 | test_after_background_helper_launches(true, "frozen_background", ^{ | |
1243 | reset_interval(); | |
1244 | T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a thaw percentage of 0"); | |
1245 | move_to_idle_band(child_pid); | |
1246 | ret = pid_suspend(child_pid); | |
1247 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended"); | |
1248 | freeze_process(child_pid); | |
1249 | ret = pid_resume(child_pid); | |
1250 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze"); | |
1251 | int percentage_after_thaw = memorystatus_freezer_thaw_percentage(); | |
1252 | T_QUIET; T_ASSERT_GT(percentage_after_thaw, 0, "thaw percentage is higher after thaw"); | |
1253 | ||
1254 | ret = pid_suspend(child_pid); | |
1255 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended"); | |
1256 | freeze_process(child_pid); | |
1257 | ret = pid_resume(child_pid); | |
1258 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze"); | |
1259 | T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), percentage_after_thaw, "thaw percentage is unchanged after second thaw"); | |
1260 | ||
1261 | ret = pid_suspend(child_pid); | |
1262 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended"); | |
1263 | freeze_process(child_pid); | |
1264 | reset_interval(); | |
1265 | T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a 0 thaw percentage"); | |
1266 | ret = pid_resume(child_pid); | |
1267 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze"); | |
1268 | T_QUIET; T_ASSERT_GT(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage goes back up in new interval"); | |
1269 | ||
1270 | T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "failed to kill child"); | |
1271 | T_END; | |
1272 | }); | |
1273 | }); | |
1274 | ||
1275 | second_child = launch_background_helper("frozen_background"); | |
1276 | T_ATEND(cleanup_memorystatus_freezer_thaw_percentage); | |
1277 | dispatch_activate(first_signal_block); | |
1278 | dispatch_main(); | |
2a1bd2d3 | 1279 | } |