1 #include <stdio.h>
2 #include <signal.h>
3 #include <sys/proc.h>
4 #include <sys/sysctl.h>
5 #include <sys/kern_memorystatus.h>
6 #include <sys/kern_memorystatus_freeze.h>
7 #include <time.h>
8 #include <mach-o/dyld.h>
9 #include <mach/mach_vm.h>
10 #include <mach/vm_page_size.h>
11 #include <mach/shared_region.h>
12 #include <mach/mach.h>
13 #include <os/reason_private.h>
14
15 #ifdef T_NAMESPACE
16 #undef T_NAMESPACE
17 #endif
18 #include <darwintest.h>
19 #include <darwintest_utils.h>
20
21 #include "memorystatus_assertion_helpers.h"
22 #include "test_utils.h"
23
24 T_GLOBAL_META(
25 T_META_NAMESPACE("xnu.vm"),
26 T_META_RADAR_COMPONENT_NAME("xnu"),
27 T_META_RADAR_COMPONENT_VERSION("VM"),
28 T_META_CHECK_LEAKS(false)
29 );
30
31 #define MEM_SIZE_MB 10
32 #define NUM_ITERATIONS 5
33 #define FREEZE_PAGES_MAX 256
34
35 #define CREATE_LIST(X) \
36 X(SUCCESS) \
37 X(TOO_FEW_ARGUMENTS) \
38 X(SYSCTL_VM_PAGESIZE_FAILED) \
39 X(VM_PAGESIZE_IS_ZERO) \
40 X(DISPATCH_SOURCE_CREATE_FAILED) \
41 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
42 X(SIGNAL_TO_PARENT_FAILED) \
43 X(MEMORYSTATUS_CONTROL_FAILED) \
44 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
45 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
46 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
47 X(FROZEN_BIT_SET) \
48 X(FROZEN_BIT_NOT_SET) \
49 X(MEMORYSTATUS_CONTROL_ERROR) \
50 X(UNABLE_TO_ALLOCATE) \
51 X(EXIT_CODE_MAX) \
52
53 #define EXIT_CODES_ENUM(VAR) VAR,
54 enum exit_codes_num {
55 CREATE_LIST(EXIT_CODES_ENUM)
56 };
57
58 #define EXIT_CODES_STRING(VAR) #VAR,
59 static const char *exit_codes_str[] = {
60 CREATE_LIST(EXIT_CODES_STRING)
61 };
62
63 static int
get_vmpage_size()64 get_vmpage_size()
65 {
66 int vmpage_size;
67 size_t size = sizeof(vmpage_size);
68 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
69 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
70 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
71 return vmpage_size;
72 }
73
74 static pid_t child_pid = -1;
75 static int freeze_count = 0;
76
77 void move_to_idle_band(pid_t);
78 void run_freezer_test(int);
79 void freeze_helper_process(void);
80 /* Gets and optionally sets the freeze pages max threshold */
81 int sysctl_freeze_pages_max(int* new_value);
82
83 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
84 * We're moving away from those unit tests, so they're copied here.
85 */
86
87 /* Cribbed from 'top'... */
88 static int
in_shared_region(mach_vm_address_t addr,cpu_type_t type)89 in_shared_region(mach_vm_address_t addr, cpu_type_t type)
90 {
91 mach_vm_address_t base = 0, size = 0;
92
93 switch (type) {
94 case CPU_TYPE_ARM:
95 base = SHARED_REGION_BASE_ARM;
96 size = SHARED_REGION_SIZE_ARM;
97 break;
98
99 case CPU_TYPE_ARM64:
100 base = SHARED_REGION_BASE_ARM64;
101 size = SHARED_REGION_SIZE_ARM64;
102 break;
103
104
105 case CPU_TYPE_X86_64:
106 base = SHARED_REGION_BASE_X86_64;
107 size = SHARED_REGION_SIZE_X86_64;
108 break;
109
110 case CPU_TYPE_I386:
111 base = SHARED_REGION_BASE_I386;
112 size = SHARED_REGION_SIZE_I386;
113 break;
114
115 case CPU_TYPE_POWERPC:
116 base = SHARED_REGION_BASE_PPC;
117 size = SHARED_REGION_SIZE_PPC;
118 break;
119
120 case CPU_TYPE_POWERPC64:
121 base = SHARED_REGION_BASE_PPC64;
122 size = SHARED_REGION_SIZE_PPC64;
123 break;
124
125 default: {
126 int t = type;
127
128 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
129 abort();
130 }
131 }
132
133 return addr >= base && addr < (base + size);
134 }
135
136 /* Get the resident private memory of the given pid */
137 static unsigned long long
get_rprvt(pid_t pid)138 get_rprvt(pid_t pid)
139 {
140 mach_port_name_t task;
141 kern_return_t kr;
142
143 mach_vm_size_t rprvt = 0;
144 mach_vm_size_t empty = 0;
145 mach_vm_size_t fw_private = 0;
146 mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported
147 // in terms of vm_kernel_page_size.
148 mach_vm_size_t regs = 0;
149
150 mach_vm_address_t addr;
151 mach_vm_size_t size;
152
153 int split = 0;
154
155 kr = task_for_pid(mach_task_self(), pid, &task);
156 T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child");
157
158 for (addr = 0;; addr += size) {
159 vm_region_top_info_data_t info;
160 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
161 mach_port_t object_name;
162
163 kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
164 if (kr != KERN_SUCCESS) {
165 break;
166 }
167
168 #if defined (__arm64__)
169 if (in_shared_region(addr, CPU_TYPE_ARM64)) {
170 #else
171 if (in_shared_region(addr, CPU_TYPE_ARM)) {
172 #endif
173 // Private Shared
174 fw_private += info.private_pages_resident * pagesize;
175
176 /*
177 * Check if this process has the globally shared
178 * text and data regions mapped in. If so, set
179 * split to TRUE and avoid checking
180 * again.
181 */
182 if (split == FALSE && info.share_mode == SM_EMPTY) {
183 vm_region_basic_info_data_64_t b_info;
184 mach_vm_address_t b_addr = addr;
185 mach_vm_size_t b_size = size;
186 count = VM_REGION_BASIC_INFO_COUNT_64;
187
188 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name);
189 if (kr != KERN_SUCCESS) {
190 break;
191 }
192
193 if (b_info.reserved) {
194 split = TRUE;
195 }
196 }
197
198 /*
199 * Short circuit the loop if this isn't a shared
200 * private region, since that's the only region
201 * type we care about within the current address
202 * range.
203 */
204 if (info.share_mode != SM_PRIVATE) {
205 continue;
206 }
207 }
208
209 regs++;
210
211 /*
212 * Update counters according to the region type.
213 */
214
215 if (info.share_mode == SM_COW && info.ref_count == 1) {
216 // Treat single reference SM_COW as SM_PRIVATE
217 info.share_mode = SM_PRIVATE;
218 }
219
220 switch (info.share_mode) {
221 case SM_LARGE_PAGE:
222 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
223 // since they are not shareable and are wired.
224 case SM_PRIVATE:
225 rprvt += info.private_pages_resident * pagesize;
226 rprvt += info.shared_pages_resident * pagesize;
227 break;
228
229 case SM_EMPTY:
230 empty += size;
231 break;
232
233 case SM_COW:
234 case SM_SHARED:
235 if (pid == 0) {
236 // Treat kernel_task specially
237 if (info.share_mode == SM_COW) {
238 rprvt += info.private_pages_resident * pagesize;
239 }
240 break;
241 }
242
243 if (info.share_mode == SM_COW) {
244 rprvt += info.private_pages_resident * pagesize;
245 }
246 break;
247
248 default:
249 assert(0);
250 break;
251 }
252 }
253
254 return rprvt;
255 }
256
257 void
258 move_to_idle_band(pid_t pid)
259 {
260 memorystatus_priority_properties_t props;
261 /*
262 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
263 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
264 */
265 props.priority = JETSAM_PRIORITY_IDLE;
266 props.user_data = 0;
267
268 /*
269 * This requires us to run as root (in the absence of entitlement).
270 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
271 */
272 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props))) {
273 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
274 }
275 }
276
277 void
278 freeze_helper_process(void)
279 {
280 size_t length;
281 int ret, freeze_enabled, errno_freeze_sysctl;
282 uint64_t resident_memory_before, resident_memory_after, vmpage_size;
283 vmpage_size = (uint64_t) get_vmpage_size();
284 resident_memory_before = get_rprvt(child_pid) / vmpage_size;
285
286 T_LOG("Freezing child pid %d", child_pid);
287 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
288 errno_freeze_sysctl = errno;
289 sleep(1);
290
291 /*
292 * The child process toggles its freezable state on each iteration.
293 * So a failure for every alternate freeze is expected.
294 */
295 if (freeze_count % 2) {
296 length = sizeof(freeze_enabled);
297 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
298 "failed to query vm.freeze_enabled");
299 if (freeze_enabled) {
300 errno = errno_freeze_sysctl;
301 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
302 } else {
303 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
304 T_LOG("Freeze has been disabled. Terminating early.");
305 T_END;
306 }
307 resident_memory_after = get_rprvt(child_pid) / vmpage_size;
308 uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL);
309 T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set");
310 if (resident_memory_before > freeze_pages_max) {
311 T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold.");
312 }
313 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
314 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
315 } else {
316 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
317 T_LOG("Freeze failed as expected");
318 }
319
320 freeze_count++;
321
322 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
323 }
324
325 static void
326 skip_if_freezer_is_disabled()
327 {
328 int freeze_enabled;
329 size_t length = sizeof(freeze_enabled);
330
331 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
332 "failed to query vm.freeze_enabled");
333 if (!freeze_enabled) {
334 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
335 T_SKIP("Freeze has been disabled. Skipping test.");
336 }
337 }
338
339 void
340 run_freezer_test(int num_pages)
341 {
342 int ret;
343 char sz_str[50];
344 char **launch_tool_args;
345 char testpath[PATH_MAX];
346 uint32_t testpath_buf_size;
347 dispatch_source_t ds_freeze, ds_proc;
348
349 skip_if_freezer_is_disabled();
350
351 signal(SIGUSR1, SIG_IGN);
352 ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
353 T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
354
355 dispatch_source_set_event_handler(ds_freeze, ^{
356 if (freeze_count < NUM_ITERATIONS) {
357 freeze_helper_process();
358 } else {
359 kill(child_pid, SIGKILL);
360 dispatch_source_cancel(ds_freeze);
361 }
362 });
363 dispatch_activate(ds_freeze);
364
365 testpath_buf_size = sizeof(testpath);
366 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
367 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
368 T_LOG("Executable path: %s", testpath);
369
370 sprintf(sz_str, "%d", num_pages);
371 launch_tool_args = (char *[]){
372 testpath,
373 "-n",
374 "allocate_pages",
375 "--",
376 sz_str,
377 NULL
378 };
379
380 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
381 ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
382 if (ret != 0) {
383 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
384 }
385 T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
386
387 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
388 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
389
390 dispatch_source_set_event_handler(ds_proc, ^{
391 int status = 0, code = 0;
392 pid_t rc = waitpid(child_pid, &status, 0);
393 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
394 code = WEXITSTATUS(status);
395
396 if (code == 0) {
397 T_END;
398 } else if (code > 0 && code < EXIT_CODE_MAX) {
399 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
400 } else {
401 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
402 }
403 });
404 dispatch_activate(ds_proc);
405
406 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
407 dispatch_main();
408 }
409
410 static void
411 allocate_pages(int num_pages)
412 {
413 int i, j, vmpgsize;
414 char val;
415 __block int num_iter = 0;
416 __block char **buf;
417 dispatch_source_t ds_signal;
418 vmpgsize = get_vmpage_size();
419 if (num_pages < 1) {
420 printf("Invalid number of pages to allocate: %d\n", num_pages);
421 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
422 }
423
424 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
425
426 /* Gives us the compression ratio we see in the typical case (~2.7) */
427 for (j = 0; j < num_pages; j++) {
428 buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
429 val = 0;
430 for (i = 0; i < vmpgsize; i += 16) {
431 memset(&buf[j][i], val, 16);
432 if (i < 3400 * (vmpgsize / 4096)) {
433 val++;
434 }
435 }
436 }
437
438 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
439 /* Signal to the parent that we're done allocating and it's ok to freeze us */
440 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
441 if (kill(getppid(), SIGUSR1) != 0) {
442 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
443 }
444 });
445
446 signal(SIGUSR1, SIG_IGN);
447 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
448 if (ds_signal == NULL) {
449 exit(DISPATCH_SOURCE_CREATE_FAILED);
450 }
451
452 dispatch_source_set_event_handler(ds_signal, ^{
453 int current_state, new_state;
454 volatile int tmp;
455
456 /* Make sure all the pages are accessed before trying to freeze again */
457 for (int x = 0; x < num_pages; x++) {
458 tmp = buf[x][0];
459 }
460
461 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
462 /* Sysprocs start off as unfreezable. Verify that first. */
463 if (num_iter == 0 && current_state != 0) {
464 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
465 }
466
467 /* Toggle freezable state */
468 new_state = (current_state) ? 0: 1;
469 printf("[%d] Changing state from %s to %s\n", getpid(),
470 (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
471 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
472 exit(MEMORYSTATUS_CONTROL_FAILED);
473 }
474
475 /* Verify that the state has been set correctly */
476 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
477 if (new_state != current_state) {
478 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
479 }
480 num_iter++;
481
482 if (kill(getppid(), SIGUSR1) != 0) {
483 exit(SIGNAL_TO_PARENT_FAILED);
484 }
485 });
486 dispatch_activate(ds_signal);
487 move_to_idle_band(getpid());
488
489 dispatch_main();
490 }
491
492 T_HELPER_DECL(allocate_pages,
493 "allocates pages to freeze",
494 T_META_ASROOT(true)) {
495 if (argc < 1) {
496 exit(TOO_FEW_ARGUMENTS);
497 }
498
499 int num_pages = atoi(argv[0]);
500 allocate_pages(num_pages);
501 }
502
503 T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) {
504 run_freezer_test(
505 (MEM_SIZE_MB << 20) / get_vmpage_size());
506 }
507
508 static int old_freeze_pages_max = 0;
509 static void
510 reset_freeze_pages_max()
511 {
512 if (old_freeze_pages_max != 0) {
513 sysctl_freeze_pages_max(&old_freeze_pages_max);
514 }
515 }
516
517 int
518 sysctl_freeze_pages_max(int* new_value)
519 {
520 static int set_end_handler = false;
521 int freeze_pages_max, ret;
522 size_t size = sizeof(freeze_pages_max);
523 ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size);
524 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max");
525 if (!set_end_handler) {
526 // Save the original value and instruct darwintest to restore it after the test completes
527 old_freeze_pages_max = freeze_pages_max;
528 T_ATEND(reset_freeze_pages_max);
529 set_end_handler = true;
530 }
531 return old_freeze_pages_max;
532 }
533
534 T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
535 int freeze_pages_max = FREEZE_PAGES_MAX;
536 sysctl_freeze_pages_max(&freeze_pages_max);
537 run_freezer_test(FREEZE_PAGES_MAX * 2);
538 }
539
540 T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) {
541 kern_return_t kern_ret;
542 /* Set the process to freezable */
543 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
544 T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable");
545 /* Signal to our parent that we can be frozen */
546 if (kill(getppid(), SIGUSR1) != 0) {
547 T_LOG("Unable to signal to parent process!");
548 exit(1);
549 }
550 while (1) {
551 ;
552 }
553 }
554
555 static void
556 freeze_process(pid_t pid)
557 {
558 int ret, freeze_enabled, errno_freeze_sysctl;
559 size_t length;
560 T_LOG("Freezing pid %d", pid);
561
562 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
563 errno_freeze_sysctl = errno;
564 length = sizeof(freeze_enabled);
565 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
566 "failed to query vm.freeze_enabled");
567 if (freeze_enabled) {
568 errno = errno_freeze_sysctl;
569 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
570 } else {
571 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
572 T_LOG("Freeze has been disabled. Terminating early.");
573 T_END;
574 }
575 }
576
577 static uint32_t max_frozen_demotions_daily_default;
578
579 static void
580 reset_max_frozen_demotions_daily()
581 {
582 int sysctl_ret = sysctlbyname("kern.memorystatus_max_freeze_demotions_daily", NULL, NULL, &max_frozen_demotions_daily_default, sizeof(max_frozen_demotions_daily_default));
583 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "set kern.memorystatus_max_freeze_demotions_daily to default");
584 }
585
586 static void
587 allow_unlimited_demotions()
588 {
589 size_t size = sizeof(max_frozen_demotions_daily_default);
590 uint32_t new_value = UINT32_MAX;
591 int sysctl_ret = sysctlbyname("kern.memorystatus_max_freeze_demotions_daily", &max_frozen_demotions_daily_default, &size, &new_value, sizeof(new_value));
592 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "kern.memorystatus_max_freeze_demotions_daily = UINT32_MAX");
593 T_ATEND(reset_max_frozen_demotions_daily);
594 }
595
596 static void
597 memorystatus_assertion_test_demote_frozen()
598 {
599 /*
600 * 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.
601 * Then remove thee assertion, and ensure it gets demoted properly.
602 */
603 /* these values will remain fixed during testing */
604 int active_limit_mb = 15; /* arbitrary */
605 int inactive_limit_mb = 7; /* arbitrary */
606 __block int demote_value = 1;
607 /* Launch the child process, and elevate its priority */
608 int requestedpriority;
609 dispatch_source_t ds_signal, ds_exit;
610 requestedpriority = JETSAM_PRIORITY_FREEZER;
611 allow_unlimited_demotions();
612
613 /* Wait for the child process to tell us that it's ready, and then freeze it */
614 signal(SIGUSR1, SIG_IGN);
615 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
616 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
617 dispatch_source_set_event_handler(ds_signal, ^{
618 int sysctl_ret;
619 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
620 freeze_process(child_pid);
621 /* Agressive demotion */
622 sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value));
623 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
624 /* Check */
625 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
626 T_LOG("Relinquishing our assertion.");
627 /* Relinquish our assertion, and check that it gets demoted. */
628 relinquish_assertion_priority(child_pid, 0x0);
629 (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished.");
630 /* Kill the child */
631 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
632 T_END;
633 });
634
635 /* Launch the child process and set the initial properties on it. */
636 child_pid = launch_background_helper("frozen_background", false, true);
637 set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false);
638 set_assertion_priority(child_pid, requestedpriority, 0x0);
639 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
640 /* Listen for exit. */
641 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
642 dispatch_source_set_event_handler(ds_exit, ^{
643 int status = 0, code = 0;
644 pid_t rc = waitpid(child_pid, &status, 0);
645 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
646 code = WEXITSTATUS(status);
647 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
648 T_END;
649 });
650
651 dispatch_activate(ds_exit);
652 dispatch_activate(ds_signal);
653 dispatch_main();
654 }
655
656 T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
657 memorystatus_assertion_test_demote_frozen();
658 }
659
660 static unsigned int
661 get_freeze_daily_pages_max(void)
662 {
663 unsigned int memorystatus_freeze_daily_mb_max;
664 size_t length = sizeof(memorystatus_freeze_daily_mb_max);
665 int ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0);
666 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max");
667 return memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / vm_kernel_page_size;
668 }
669
670 static uint64_t
671 get_budget_multiplier(void)
672 {
673 uint64_t budget_multiplier = 0;
674 size_t size = sizeof(budget_multiplier);
675 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_multiplier", &budget_multiplier, &size, NULL, 0),
676 "get kern.memorystatus_freeze_budget_multiplier");
677 return budget_multiplier;
678 }
679 static void
680 set_budget_multiplier(uint64_t multiplier)
681 {
682 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_multiplier", NULL, NULL, &multiplier, sizeof(multiplier)),
683 "set kern.memorystatus_freeze_budget_multiplier");
684 }
685
686 static uint64_t original_budget_multiplier;
687 static void
688 reset_budget_multiplier(void)
689 {
690 set_budget_multiplier(original_budget_multiplier);
691 }
692
693 T_DECL(budget_replenishment, "budget replenishes properly",
694 T_META_REQUIRES_SYSCTL_NE("kern.memorystatus_freeze_daily_mb_max", UINT32_MAX)) {
695 size_t length;
696 int ret;
697 static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours
698 unsigned int memorystatus_freeze_daily_pages_max;
699 static unsigned int kFixedPointFactor = 100;
700 static unsigned int kNumSecondsInDay = 60 * 60 * 24;
701 unsigned int new_budget, expected_new_budget_pages;
702 size_t new_budget_ln;
703 vm_size_t page_size = vm_kernel_page_size;
704
705 original_budget_multiplier = get_budget_multiplier();
706 T_ATEND(reset_budget_multiplier);
707 set_budget_multiplier(100);
708 /*
709 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
710 * ago and we used up its entire budget.
711 */
712 length = sizeof(kTestIntervalSecs);
713 new_budget_ln = sizeof(new_budget);
714 ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length);
715 T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget");
716
717 memorystatus_freeze_daily_pages_max = get_freeze_daily_pages_max();
718 T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max);
719 T_LOG("page_size %lu", page_size);
720
721 /*
722 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
723 * seconds of budget.
724 */
725 expected_new_budget_pages = memorystatus_freeze_daily_pages_max;
726 T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages);
727 T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change");
728 expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay)
729 * memorystatus_freeze_daily_pages_max) / kFixedPointFactor;
730 T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages);
731 T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max);
732
733 T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly.");
734 }
735
736
737 static bool
738 is_proc_in_frozen_list(pid_t pid, char* name, size_t name_len)
739 {
740 int bytes_written;
741 bool found = false;
742 global_frozen_procs_t *frozen_procs = malloc(sizeof(global_frozen_procs_t));
743 T_QUIET; T_ASSERT_NOTNULL(frozen_procs, "malloc");
744
745 bytes_written = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_GET_PROCS, frozen_procs, sizeof(global_frozen_procs_t));
746 T_QUIET; T_ASSERT_LE((size_t) bytes_written, sizeof(global_frozen_procs_t), "Didn't overflow buffer");
747 T_QUIET; T_ASSERT_GT(bytes_written, 0, "Wrote someting");
748
749 for (size_t i = 0; i < frozen_procs->gfp_num_frozen; i++) {
750 if (frozen_procs->gfp_procs[i].fp_pid == pid) {
751 found = true;
752 strlcpy(name, frozen_procs->gfp_procs[i].fp_name, name_len);
753 }
754 }
755 return found;
756 }
757
758 static void
759 unset_testing_pid(void)
760 {
761 int ret;
762 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_UNSET_TESTING_PID, NULL, 0);
763 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Drop ownership of jetsam snapshot");
764 }
765
766 static void
767 set_testing_pid(void)
768 {
769 int ret;
770 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_SET_TESTING_PID, NULL, 0);
771 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Take ownership of jetsam snapshot");
772 T_ATEND(unset_testing_pid);
773 }
774
775 /*
776 * Retrieve a jetsam snapshot.
777 *
778 * return:
779 * pointer to snapshot.
780 *
781 * Caller is responsible for freeing snapshot.
782 */
783 static
784 memorystatus_jetsam_snapshot_t *
785 get_jetsam_snapshot(uint32_t flags, bool empty_allowed)
786 {
787 memorystatus_jetsam_snapshot_t * snapshot = NULL;
788 int ret;
789 uint32_t size;
790
791 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, NULL, 0);
792 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Get jetsam snapshot size");
793 size = (uint32_t) ret;
794 if (size == 0 && empty_allowed) {
795 return snapshot;
796 }
797
798 snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size);
799 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Allocate snapshot of size %d", size);
800
801 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, snapshot, size);
802 T_QUIET; T_ASSERT_GT(size, 0, "Get jetsam snapshot");
803
804 if (((size - sizeof(memorystatus_jetsam_snapshot_t)) / sizeof(memorystatus_jetsam_snapshot_entry_t)) != snapshot->entry_count) {
805 T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size,
806 sizeof(memorystatus_jetsam_snapshot_t), snapshot->entry_count, sizeof(memorystatus_jetsam_snapshot_entry_t),
807 sizeof(memorystatus_jetsam_snapshot_t) + (snapshot->entry_count * sizeof(memorystatus_jetsam_snapshot_entry_t)));
808 if (snapshot) {
809 free(snapshot);
810 }
811 }
812
813 return snapshot;
814 }
815
816 /*
817 * Look for the given pid in the snapshot.
818 *
819 * return:
820 * pointer to pid's entry or NULL if pid is not found.
821 *
822 * Caller has ownership of snapshot before and after call.
823 */
824 static
825 memorystatus_jetsam_snapshot_entry_t *
826 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t *snapshot, pid_t pid)
827 {
828 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Got snapshot");
829 for (size_t i = 0; i < snapshot->entry_count; i++) {
830 memorystatus_jetsam_snapshot_entry_t *curr = &(snapshot->entries[i]);
831 if (curr->pid == pid) {
832 return curr;
833 }
834 }
835
836 return NULL;
837 }
838
839 static void
840 resume_and_kill_proc(pid_t pid)
841 {
842 int ret = pid_resume(pid);
843 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "proc resumed after freeze");
844 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "Killed process");
845 }
846
847 static void
848 resume_and_kill_child()
849 {
850 /* Used for test cleanup. proc might not be suspended so pid_resume might fail. */
851 pid_resume(child_pid);
852 kill(child_pid, SIGKILL);
853 }
854
855 static dispatch_source_t
856 run_block_after_signal(int sig, dispatch_block_t block)
857 {
858 dispatch_source_t ds_signal;
859 signal(sig, SIG_IGN);
860 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) sig, 0, dispatch_get_main_queue());
861 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
862 dispatch_source_set_event_handler(ds_signal, block);
863 return ds_signal;
864 }
865
866 /*
867 * Launches the child & runs the given block after the child signals.
868 * If exit_with_child is true, the test will exit when the child exits.
869 */
870 static void
871 test_after_background_helper_launches(bool exit_with_child, const char* variant, dispatch_block_t test_block)
872 {
873 dispatch_source_t ds_signal, ds_exit;
874
875 ds_signal = run_block_after_signal(SIGUSR1, test_block);
876 /* Launch the child process. */
877 child_pid = launch_background_helper(variant, false, true);
878 T_ATEND(resume_and_kill_child);
879 /* Listen for exit. */
880 if (exit_with_child) {
881 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
882 dispatch_source_set_event_handler(ds_exit, ^{
883 int status = 0, code = 0;
884 pid_t rc = waitpid(child_pid, &status, 0);
885 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
886 code = WEXITSTATUS(status);
887 if (code != 0) {
888 T_LOG("Child exited with error: %s", exit_codes_str[code]);
889 }
890 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
891 T_END;
892 });
893
894 dispatch_activate(ds_exit);
895 }
896 dispatch_activate(ds_signal);
897 }
898
899 T_DECL(get_frozen_procs, "List processes in the freezer") {
900 skip_if_freezer_is_disabled();
901
902 test_after_background_helper_launches(true, "frozen_background", ^{
903 proc_name_t name;
904 /* Place the child in the idle band so that it gets elevated like a typical app. */
905 move_to_idle_band(child_pid);
906 /* Freeze the process, and check that it's in the list of frozen processes. */
907 freeze_process(child_pid);
908 /* Check */
909 T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
910 T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
911 /* Kill the child */
912 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
913 T_END;
914 });
915 dispatch_main();
916 }
917
918 T_DECL(frozen_to_swap_accounting, "jetsam snapshot has frozen_to_swap accounting") {
919 static const size_t kSnapshotSleepDelay = 5;
920 static const size_t kFreezeToDiskMaxDelay = 60;
921
922 skip_if_freezer_is_disabled();
923
924 test_after_background_helper_launches(true, "frozen_background", ^{
925 memorystatus_jetsam_snapshot_t *snapshot = NULL;
926 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
927 /* Place the child in the idle band so that it gets elevated like a typical app. */
928 move_to_idle_band(child_pid);
929 freeze_process(child_pid);
930 /*
931 * Wait until the child's pages get paged out to disk.
932 * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds,
933 * something is either wrong with the compactor or the accounting.
934 */
935 for (size_t i = 0; i < kFreezeToDiskMaxDelay / kSnapshotSleepDelay; i++) {
936 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
937 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
938 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
939 if (child_entry->jse_frozen_to_swap_pages > 0) {
940 break;
941 }
942 free(snapshot);
943 sleep(kSnapshotSleepDelay);
944 }
945 T_QUIET; T_ASSERT_GT(child_entry->jse_frozen_to_swap_pages, 0ULL, "child has some pages in swap");
946 free(snapshot);
947 /* Kill the child */
948 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
949 T_END;
950 });
951 dispatch_main();
952 }
953
954 T_DECL(freezer_snapshot, "App kills are recorded in the freezer snapshot") {
955 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
956 set_testing_pid();
957
958 test_after_background_helper_launches(false, "frozen_background", ^{
959 int ret;
960 memorystatus_jetsam_snapshot_t *snapshot = NULL;
961 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
962
963 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
964 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
965
966 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
967 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
968 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
969 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
970 T_QUIET; T_ASSERT_EQ(child_entry->killed, (unsigned long long) JETSAM_REASON_GENERIC, "Child entry was killed");
971
972 free(snapshot);
973 T_END;
974 });
975 dispatch_main();
976 }
977
978 T_DECL(freezer_snapshot_consume, "Freezer snapshot is consumed on read") {
979 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
980 set_testing_pid();
981
982 test_after_background_helper_launches(false, "frozen_background", ^{
983 int ret;
984 memorystatus_jetsam_snapshot_t *snapshot = NULL;
985 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
986
987 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
988 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
989
990 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
991 T_ASSERT_NOTNULL(snapshot, "Got first freezer snapshot");
992 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
993 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in first freezer snapshot");
994
995 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, true);
996 if (snapshot != NULL) {
997 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
998 T_QUIET; T_ASSERT_NULL(child_entry, "Child is not in second freezer snapshot");
999 }
1000
1001 free(snapshot);
1002 T_END;
1003 });
1004 dispatch_main();
1005 }
1006
1007 T_DECL(freezer_snapshot_frozen_state, "Frozen state is recorded in freezer snapshot") {
1008 skip_if_freezer_is_disabled();
1009 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1010 set_testing_pid();
1011
1012 test_after_background_helper_launches(false, "frozen_background", ^{
1013 int ret;
1014 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1015 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1016
1017 move_to_idle_band(child_pid);
1018 freeze_process(child_pid);
1019
1020 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1021 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1022
1023 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1024 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1025 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1026 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1027 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is set");
1028
1029 free(snapshot);
1030 T_END;
1031 });
1032 dispatch_main();
1033 }
1034
1035 T_DECL(freezer_snapshot_thaw_state, "Thaw count is recorded in freezer snapshot") {
1036 skip_if_freezer_is_disabled();
1037 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1038 set_testing_pid();
1039
1040 test_after_background_helper_launches(false, "frozen_background", ^{
1041 int ret;
1042 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1043 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1044
1045 move_to_idle_band(child_pid);
1046 ret = pid_suspend(child_pid);
1047 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1048 freeze_process(child_pid);
1049 ret = pid_resume(child_pid);
1050 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1051
1052 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1053 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1054
1055 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1056 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1057 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1058 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1059 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is still set after thaw");
1060 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusWasThawed, "Child entry was thawed");
1061 T_QUIET; T_ASSERT_EQ(child_entry->jse_thaw_count, 1ULL, "Child entry's thaw count was incremented");
1062
1063 free(snapshot);
1064 T_END;
1065 });
1066 dispatch_main();
1067 }
1068
1069 T_HELPER_DECL(check_frozen, "Check frozen state", T_META_ASROOT(true)) {
1070 int kern_ret;
1071 dispatch_source_t ds_signal;
1072 __block int is_frozen;
1073 /* Set the process to freezable */
1074 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
1075 T_QUIET; T_ASSERT_POSIX_SUCCESS(kern_ret, "set process is freezable");
1076
1077 /* We should not be frozen yet. */
1078 is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1079 if (is_frozen == -1) {
1080 T_LOG("memorystatus_control error: %s", strerror(errno));
1081 exit(MEMORYSTATUS_CONTROL_ERROR);
1082 }
1083 if (is_frozen) {
1084 exit(FROZEN_BIT_SET);
1085 }
1086
1087 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
1088 if (ds_signal == NULL) {
1089 exit(DISPATCH_SOURCE_CREATE_FAILED);
1090 }
1091
1092 dispatch_source_set_event_handler(ds_signal, ^{
1093 /* We should now be frozen. */
1094 is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1095 if (is_frozen == -1) {
1096 T_LOG("memorystatus_control error: %s", strerror(errno));
1097 exit(MEMORYSTATUS_CONTROL_ERROR);
1098 }
1099 if (!is_frozen) {
1100 exit(FROZEN_BIT_NOT_SET);
1101 }
1102 exit(SUCCESS);
1103 });
1104 dispatch_activate(ds_signal);
1105
1106 sig_t sig_ret = signal(SIGUSR1, SIG_IGN);
1107 T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)");
1108
1109 /* Signal to our parent that we can be frozen */
1110 if (kill(getppid(), SIGUSR1) != 0) {
1111 T_LOG("Unable to signal to parent process!");
1112 exit(SIGNAL_TO_PARENT_FAILED);
1113 }
1114
1115 dispatch_main();
1116 }
1117
1118 T_DECL(memorystatus_get_process_is_frozen, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state") {
1119 skip_if_freezer_is_disabled();
1120
1121 test_after_background_helper_launches(true, "check_frozen", ^{
1122 int ret;
1123 /* Freeze the child, resume it, and signal it to check its state */
1124 move_to_idle_band(child_pid);
1125 ret = pid_suspend(child_pid);
1126 T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1127 freeze_process(child_pid);
1128 ret = pid_resume(child_pid);
1129 T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1130
1131 kill(child_pid, SIGUSR1);
1132 /* The child will checks its own frozen state & exit. */
1133 });
1134 dispatch_main();
1135 }
1136
1137 static unsigned int freeze_pages_min_old;
1138 static int throttle_enabled_old;
1139 static void
1140 cleanup_memorystatus_freeze_top_process()
1141 {
1142 sysctlbyname("kern.memorystatus_freeze_pages_min", NULL, NULL, &freeze_pages_min_old, sizeof(freeze_pages_min_old));
1143 sysctlbyname("kern.memorystatus_freeze_throttle_enabled", NULL, NULL, &throttle_enabled_old, sizeof(throttle_enabled_old));
1144 }
1145
1146 /*
1147 * Disables heuristics that could prevent us from freezing the child via memorystatus_freeze_top_process.
1148 */
1149 static void
1150 memorystatus_freeze_top_process_setup()
1151 {
1152 size_t freeze_pages_min_size = sizeof(freeze_pages_min_old);
1153 unsigned int freeze_pages_min_new = 0;
1154 size_t throttle_enabled_old_size = sizeof(throttle_enabled_old);
1155 int throttle_enabled_new = 1, ret;
1156
1157 ret = sysctlbyname("kern.memorystatus_freeze_pages_min", &freeze_pages_min_old, &freeze_pages_min_size, &freeze_pages_min_new, sizeof(freeze_pages_min_new));
1158 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_pages_min");
1159 ret = sysctlbyname("kern.memorystatus_freeze_throttle_enabled", &throttle_enabled_old, &throttle_enabled_old_size, &throttle_enabled_new, sizeof(throttle_enabled_new));
1160 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_throttle_enabled");
1161 T_ATEND(cleanup_memorystatus_freeze_top_process);
1162 /* Take ownership of the freezer probabilities for the duration of the test so that we don't race with dasd. */
1163 set_testing_pid();
1164 }
1165
1166 /*
1167 * Moves the proc to the idle band and suspends it.
1168 */
1169 static void
1170 prepare_proc_for_freezing(pid_t pid)
1171 {
1172 move_to_idle_band(pid);
1173 int ret = pid_suspend(pid);
1174 T_ASSERT_POSIX_SUCCESS(ret, "proc suspended");
1175 }
1176
1177 #define P_MEMSTAT_FROZEN 0x00000002
1178 static void
1179 verify_proc_frozen_state(pid_t pid, bool expected)
1180 {
1181 memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1182 memorystatus_jetsam_snapshot_entry_t *entry = get_jetsam_snapshot_entry(snapshot, pid);
1183 T_ASSERT_NOTNULL(entry, "%d is in snapshot", pid);
1184 bool is_frozen = (entry->state & P_MEMSTAT_FROZEN) != 0;
1185 if (is_frozen != expected) {
1186 T_LOG("%s frozen state is wrong. Expected %d, got %d. Skip reason: %d. Jetsam band: %d", entry->name, expected, is_frozen, entry->jse_freeze_skip_reason, entry->priority);
1187 }
1188 T_ASSERT_EQ(is_frozen, expected, "%s frozen state", entry->name);
1189 free(snapshot);
1190 }
1191
1192 static void
1193 verify_proc_is_frozen(pid_t pid)
1194 {
1195 verify_proc_frozen_state(pid, true);
1196 }
1197
1198 static void
1199 verify_proc_not_frozen(pid_t pid)
1200 {
1201 verify_proc_frozen_state(pid, false);
1202 }
1203
1204 T_DECL(memorystatus_freeze_top_process, "memorystatus_freeze_top_process chooses the correct process",
1205 T_META_ASROOT(true),
1206 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1207 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1208 T_SKIP("Skipping flaky test"); // rdar://76986376
1209 int32_t memorystatus_freeze_band = 0;
1210 size_t memorystatus_freeze_band_size = sizeof(memorystatus_freeze_band);
1211 __block errno_t ret;
1212 __block int maxproc;
1213 size_t maxproc_size = sizeof(maxproc);
1214
1215 ret = sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, NULL, 0);
1216 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.maxproc");
1217 ret = sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band, &memorystatus_freeze_band_size, NULL, 0);
1218 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_jetsam_band");
1219
1220 memorystatus_freeze_top_process_setup();
1221 test_after_background_helper_launches(true, "frozen_background", ^{
1222 int32_t child_band = JETSAM_PRIORITY_DEFAULT;
1223 prepare_proc_for_freezing(child_pid);
1224
1225 size_t buffer_len = sizeof(memorystatus_properties_entry_v1_t) * (size_t) maxproc;
1226 memorystatus_properties_entry_v1_t *properties_list = malloc(buffer_len);
1227 T_QUIET; T_ASSERT_NOTNULL(properties_list, "malloc properties array");
1228 size_t properties_list_len = 0;
1229 /* The child needs to age down into the idle band before it's eligible to be frozen. */
1230 T_LOG("Waiting for child to age into the idle band.");
1231 while (child_band != JETSAM_PRIORITY_IDLE) {
1232 memset(properties_list, 0, buffer_len);
1233 properties_list_len = 0;
1234 memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1235
1236 bool found = false;
1237 for (size_t i = 0; i < snapshot->entry_count; i++) {
1238 memorystatus_jetsam_snapshot_entry_t *snapshot_entry = &snapshot->entries[i];
1239 if (snapshot_entry->priority <= memorystatus_freeze_band && !snapshot_entry->killed) {
1240 pid_t pid = snapshot_entry->pid;
1241 memorystatus_properties_entry_v1_t *property_entry = &properties_list[properties_list_len++];
1242 property_entry->version = 1;
1243 property_entry->pid = pid;
1244 if (pid == child_pid) {
1245 found = true;
1246 property_entry->use_probability = 1;
1247 child_band = snapshot_entry->priority;
1248 } else {
1249 property_entry->use_probability = 0;
1250 }
1251 strncpy(property_entry->proc_name, snapshot_entry->name, MAXCOMLEN);
1252 property_entry->proc_name[MAXCOMLEN] = '\0';
1253 }
1254 }
1255 T_QUIET; T_ASSERT_TRUE(found, "Child is in on demand snapshot");
1256 free(snapshot);
1257 }
1258 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);
1259 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY");
1260 free(properties_list);
1261 int val = 1;
1262 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1263 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1264
1265 verify_proc_is_frozen(child_pid);
1266 resume_and_kill_proc(child_pid);
1267 T_END;
1268 });
1269 dispatch_main();
1270 }
1271 static unsigned int use_ordered_list_original;
1272 static unsigned int use_demotion_list_original;
1273 static void
1274 reset_ordered_freeze_mode()
1275 {
1276 sysctlbyname("kern.memorystatus_freezer_use_ordered_list", NULL, NULL, &use_ordered_list_original, sizeof(use_ordered_list_original));
1277 }
1278
1279 static void
1280 reset_ordered_demote_mode()
1281 {
1282 sysctlbyname("kern.memorystatus_freezer_use_demotion_list", NULL, NULL, &use_demotion_list_original, sizeof(use_demotion_list_original));
1283 }
1284
1285 static void
1286 enable_ordered_freeze_mode()
1287 {
1288 int ret;
1289 int val = 1;
1290 size_t size = sizeof(use_ordered_list_original);
1291 ret = sysctlbyname("kern.memorystatus_freezer_use_ordered_list", &use_ordered_list_original, &size, &val, sizeof(val));
1292 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freezer_use_ordered_list");
1293 T_ATEND(reset_ordered_freeze_mode);
1294 }
1295
1296 static void
1297 enable_ordered_demote_mode()
1298 {
1299 int ret;
1300 int val = 1;
1301 size_t size = sizeof(use_demotion_list_original);
1302 ret = sysctlbyname("kern.memorystatus_freezer_use_demotion_list", &use_demotion_list_original, &size, &val, sizeof(val));
1303 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freezer_use_demotion_list");
1304 T_ATEND(reset_ordered_demote_mode);
1305 }
1306
1307 static void
1308 construct_child_freeze_entry(memorystatus_properties_freeze_entry_v1 *entry)
1309 {
1310 memset(entry, 0, sizeof(memorystatus_properties_freeze_entry_v1));
1311 entry->version = 1;
1312 entry->pid = child_pid;
1313 entry->priority = 1;
1314
1315 /* Get the child's name. */
1316 memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1317 memorystatus_jetsam_snapshot_entry_t *snapshot_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1318 strncpy(entry->proc_name, snapshot_entry->name, sizeof(entry->proc_name));
1319 free(snapshot);
1320 }
1321
1322 T_DECL(memorystatus_freeze_top_process_ordered, "memorystatus_freeze_top_process chooses the correct process when using an ordered list",
1323 T_META_ASROOT(true),
1324 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1325 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1326 memorystatus_freeze_top_process_setup();
1327 enable_ordered_freeze_mode();
1328 test_after_background_helper_launches(true, "frozen_background", ^{
1329 int ret, val = 1;
1330 memorystatus_properties_freeze_entry_v1 entries[1];
1331
1332 construct_child_freeze_entry(&entries[0]);
1333 prepare_proc_for_freezing(child_pid);
1334
1335 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1336 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1337 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1338
1339 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1340 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1341
1342 verify_proc_is_frozen(child_pid);
1343 resume_and_kill_proc(child_pid);
1344
1345 T_END;
1346 });
1347 dispatch_main();
1348 }
1349
1350 static void
1351 memorystatus_freeze_top_process_ordered_wrong_pid(pid_t (^pid_for_entry)(pid_t))
1352 {
1353 memorystatus_freeze_top_process_setup();
1354 enable_ordered_freeze_mode();
1355 test_after_background_helper_launches(true, "frozen_background", ^{
1356 int ret, val = 1;
1357 memorystatus_properties_freeze_entry_v1 entries[1];
1358
1359 construct_child_freeze_entry(&entries[0]);
1360 entries[0].pid = pid_for_entry(child_pid);
1361 prepare_proc_for_freezing(child_pid);
1362
1363 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1364 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1365 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1366
1367 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1368 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1369
1370 verify_proc_is_frozen(child_pid);
1371 resume_and_kill_proc(child_pid);
1372
1373 T_END;
1374 });
1375 dispatch_main();
1376 }
1377
1378 /*
1379 * Try both with a pid that's used by another process
1380 * and a pid that is likely unused.
1381 * In both cases the child should still get frozen.
1382 */
1383 T_DECL(memorystatus_freeze_top_process_ordered_reused_pid, "memorystatus_freeze_top_process is resilient to pid changes",
1384 T_META_ASROOT(true),
1385 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1386 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1387 memorystatus_freeze_top_process_ordered_wrong_pid(^(__unused pid_t child) {
1388 return 1;
1389 });
1390 }
1391
1392 T_DECL(memorystatus_freeze_top_process_ordered_wrong_pid, "memorystatus_freeze_top_process is resilient to pid changes",
1393 T_META_ASROOT(true),
1394 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1395 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1396 memorystatus_freeze_top_process_ordered_wrong_pid(^(__unused pid_t child) {
1397 return child + 1000;
1398 });
1399 }
1400
1401 T_DECL(memorystatus_freeze_demote_ordered, "memorystatus_demote_frozen_processes_using_demote_list chooses the correct process",
1402 T_META_ASROOT(true),
1403 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1404 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1405 memorystatus_freeze_top_process_setup();
1406 enable_ordered_freeze_mode();
1407 enable_ordered_demote_mode();
1408 test_after_background_helper_launches(true, "frozen_background", ^{
1409 int ret, val = 1;
1410 int32_t memorystatus_freeze_band = 0;
1411 size_t memorystatus_freeze_band_size = sizeof(memorystatus_freeze_band);
1412 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1413 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1414 memorystatus_properties_freeze_entry_v1 entries[1];
1415
1416 ret = sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band, &memorystatus_freeze_band_size, NULL, 0);
1417 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_jetsam_band");
1418
1419 construct_child_freeze_entry(&entries[0]);
1420 prepare_proc_for_freezing(child_pid);
1421
1422 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1423 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1424 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1425
1426 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1427 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1428
1429 verify_proc_is_frozen(child_pid);
1430
1431 /*
1432 * Place the child at the head of the demotion list.
1433 */
1434 T_LOG("Telling kernel to demote %s", entries[0].proc_name);
1435 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_DEMOTE_PRIORITY, entries, sizeof(entries));
1436 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_DEMOTE_PRIORITY");
1437
1438 /* Resume the child */
1439 ret = pid_resume(child_pid);
1440 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1441
1442 /* Trigger a demotion check */
1443 val = 1;
1444 ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &val, sizeof(val));
1445 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
1446
1447 /* Verify that the child was demoted */
1448 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1449 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1450 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
1451 T_QUIET; T_ASSERT_LT(child_entry->priority, memorystatus_freeze_band, "child was demoted");
1452 free(snapshot);
1453
1454 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed process");
1455
1456 T_END;
1457 });
1458 dispatch_main();
1459 }
1460
1461 static int
1462 memorystatus_freezer_thaw_percentage(void)
1463 {
1464 int val;
1465 size_t size = sizeof(val);
1466 int ret = sysctlbyname("kern.memorystatus_freezer_thaw_percentage", &val, &size, NULL, 0);
1467 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freezer_thaw_percentage");
1468 return val;
1469 }
1470
1471 static void
1472 reset_interval(void)
1473 {
1474 uint32_t freeze_daily_budget_mb = 0;
1475 size_t size = sizeof(freeze_daily_budget_mb);
1476 int ret;
1477 uint64_t new_budget;
1478 ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &freeze_daily_budget_mb, &size, NULL, 0);
1479 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freeze_daily_mb_max");
1480 new_budget = (freeze_daily_budget_mb * (1UL << 20) / vm_page_size);
1481 ret = sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL, NULL, &new_budget, sizeof(new_budget));
1482 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to set kern.memorystatus_freeze_budget_pages_remaining");
1483 }
1484
1485 static pid_t second_child;
1486 static void
1487 cleanup_memorystatus_freezer_thaw_percentage(void)
1488 {
1489 kill(second_child, SIGKILL);
1490 }
1491
1492 T_DECL(memorystatus_freezer_thaw_percentage, "memorystatus_freezer_thaw_percentage updates correctly",
1493 T_META_ASROOT(true),
1494 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1495 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1496 __block dispatch_source_t first_signal_block;
1497 /* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */
1498 set_testing_pid();
1499 reset_interval();
1500
1501 /* Spawn one child that will remain frozen throughout the whole test & another that will be thawed. */
1502 first_signal_block = run_block_after_signal(SIGUSR1, ^{
1503 move_to_idle_band(second_child);
1504 __block int ret = pid_suspend(second_child);
1505 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1506 freeze_process(second_child);
1507 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage is still 0 after freeze");
1508 dispatch_source_cancel(first_signal_block);
1509 test_after_background_helper_launches(true, "frozen_background", ^{
1510 reset_interval();
1511 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a thaw percentage of 0");
1512 move_to_idle_band(child_pid);
1513 ret = pid_suspend(child_pid);
1514 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1515 freeze_process(child_pid);
1516 ret = pid_resume(child_pid);
1517 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1518 int percentage_after_thaw = memorystatus_freezer_thaw_percentage();
1519 T_QUIET; T_ASSERT_GT(percentage_after_thaw, 0, "thaw percentage is higher after thaw");
1520
1521 ret = pid_suspend(child_pid);
1522 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1523 freeze_process(child_pid);
1524 ret = pid_resume(child_pid);
1525 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1526 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), percentage_after_thaw, "thaw percentage is unchanged after second thaw");
1527
1528 ret = pid_suspend(child_pid);
1529 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1530 freeze_process(child_pid);
1531 reset_interval();
1532 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a 0 thaw percentage");
1533 ret = pid_resume(child_pid);
1534 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1535 T_QUIET; T_ASSERT_GT(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage goes back up in new interval");
1536
1537 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "failed to kill child");
1538 T_END;
1539 });
1540 });
1541
1542 second_child = launch_background_helper("frozen_background", false, true);
1543 T_ATEND(cleanup_memorystatus_freezer_thaw_percentage);
1544 dispatch_activate(first_signal_block);
1545 dispatch_main();
1546 }
1547
1548 static uint64_t
1549 get_budget_pages_remaining(void)
1550 {
1551 uint64_t pages_remaining = 0;
1552 size_t size = sizeof(pages_remaining);
1553 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", &pages_remaining, &size, NULL, 0),
1554 "get kern.memorystatus_freeze_budget_pages_remaining");
1555 return pages_remaining;
1556 }
1557
1558 static void
1559 set_budget_pages_remaining(uint64_t pages_remaining)
1560 {
1561 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL, NULL, &pages_remaining, sizeof(pages_remaining)),
1562 "get kern.memorystatus_freeze_budget_pages_remaining");
1563 }
1564
1565 static void
1566 enable_freeze(void)
1567 {
1568 int freeze_enabled = 1;
1569 size_t length = sizeof(freeze_enabled);
1570 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", NULL, NULL, &freeze_enabled, length),
1571 "enable vm.freeze_enabled");
1572 }
1573
1574 T_DECL(memorystatus_freeze_budget_multiplier, "memorystatus_budget_multiplier multiplies budget",
1575 T_META_ASROOT(true),
1576 T_META_REQUIRES_SYSCTL_NE("kern.memorystatus_freeze_daily_mb_max", UINT32_MAX),
1577 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1578 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1),
1579 T_META_ENABLED(false) /* rdar://87165483 */) {
1580 /* Disable freezer so that the budget doesn't change out from underneath us. */
1581 int freeze_enabled = 0;
1582 size_t length = sizeof(freeze_enabled);
1583 uint64_t freeze_daily_pages_max;
1584 T_ATEND(enable_freeze);
1585 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", NULL, NULL, &freeze_enabled, length),
1586 "disable vm.freeze_enabled");
1587 freeze_daily_pages_max = get_freeze_daily_pages_max();
1588 original_budget_multiplier = get_budget_multiplier();
1589 T_ATEND(reset_budget_multiplier);
1590 set_budget_multiplier(100);
1591 T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max, "multiplier=100%%");
1592 set_budget_multiplier(50);
1593 T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max / 2, "multiplier=50%%");
1594 set_budget_multiplier(200);
1595 T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max * 2, "multiplier=200%%");
1596 }
1597
1598 T_DECL(memorystatus_freeze_set_dasd_trial_identifiers, "set dasd trial identifiers",
1599 T_META_ASROOT(true)) {
1600 #define TEST_STR "freezer-das-trial"
1601 memorystatus_freezer_trial_identifiers_v1 identifiers = {0};
1602 identifiers.version = 1;
1603 strncpy(identifiers.treatment_id, TEST_STR, sizeof(identifiers.treatment_id));
1604 strncpy(identifiers.experiment_id, TEST_STR, sizeof(identifiers.treatment_id));
1605 identifiers.deployment_id = 2;
1606 int ret = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_SET_DASD_TRIAL_IDENTIFIERS, &identifiers, sizeof(identifiers));
1607 T_WITH_ERRNO; T_ASSERT_EQ(ret, 0, "FREEZER_CONTROL_SET_DASD_TRIAL_IDENTIFIERS");
1608 }
1609
1610 T_DECL(memorystatus_reset_freezer_state, "FREEZER_CONTROL_RESET_STATE kills frozen proccesses",
1611 T_META_ASROOT(true),
1612 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1613 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1614 /* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */
1615 set_testing_pid();
1616 reset_interval();
1617
1618 test_after_background_helper_launches(false, "frozen_background", ^{
1619 proc_name_t name;
1620 int ret;
1621 /* Freeze the child and verify they're frozen. */
1622 move_to_idle_band(child_pid);
1623 ret = pid_suspend(child_pid);
1624 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1625 freeze_process(child_pid);
1626 T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
1627 T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
1628 /* Set the budget to 0. */
1629 set_budget_pages_remaining(0);
1630
1631 /* FREEZER_CONTROL_RESET_STATE */
1632 ret = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_RESET_STATE, NULL, 0);
1633 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "FREEZER_CONRTOL_RESET_STATE");
1634
1635 /* Verify budget > 0 */
1636 uint64_t budget_after_reset = get_budget_pages_remaining();
1637 T_QUIET; T_ASSERT_GT(budget_after_reset, 0ULL, "freeze budget after reset > 0");
1638 /*
1639 * Verify child has been killed
1640 * Note that the task termination isn't synchronous with the RESET_STATE call so we may
1641 * block in waitpid temporarily.
1642 */
1643 int stat;
1644 while (true) {
1645 pid_t wait_p = waitpid(child_pid, &stat, 0);
1646 if (wait_p == child_pid) {
1647 break;
1648 }
1649 }
1650 T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(stat), "child was signaled");
1651 T_QUIET; T_ASSERT_EQ(WTERMSIG(stat), SIGKILL, "Child received SIGKILL");
1652
1653 T_END;
1654 });
1655 dispatch_main();
1656 }
1657
1658 static void
1659 dock_proc(pid_t pid)
1660 {
1661 int ret;
1662 ret = memorystatus_control(MEMORYSTATUS_CMD_ELEVATED_INACTIVEJETSAMPRIORITY_ENABLE, pid, 0, NULL, 0);
1663 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dock_proc");
1664 }
1665
1666 T_DECL(memorystatus_freeze_skip_docked, "memorystatus_freeze_top_process does not freeze docked processes",
1667 T_META_ASROOT(true),
1668 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1669 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1670 memorystatus_freeze_top_process_setup();
1671 enable_ordered_freeze_mode();
1672 test_after_background_helper_launches(true, "frozen_background", ^{
1673 int ret, val = 1;
1674 memorystatus_properties_freeze_entry_v1 entries[1];
1675
1676 dock_proc(child_pid);
1677 construct_child_freeze_entry(&entries[0]);
1678 prepare_proc_for_freezing(child_pid);
1679
1680 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1681 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1682 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1683
1684 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1685 T_ASSERT_EQ(errno, ESRCH, "freeze_top_process errno");
1686 T_ASSERT_EQ(ret, -1, "freeze_top_process");
1687
1688 verify_proc_not_frozen(child_pid);
1689 resume_and_kill_proc(child_pid);
1690
1691 T_END;
1692 });
1693 dispatch_main();
1694 }
1695
1696 T_HELPER_DECL(corpse_generation, "Generate a large corpse", T_META_ASROOT(false)) {
1697 /*
1698 * Allocate and fault in a bunch of memory so that it takes a while
1699 * to generate our corpse.
1700 */
1701 size_t bytes_to_allocate = 304 * (1UL << 20);
1702 size_t block_size = 8 * (1UL << 20);
1703 for (size_t i = 0; i < bytes_to_allocate / block_size; i++) {
1704 unsigned char *ptr = malloc(block_size);
1705 if (ptr == NULL) {
1706 T_LOG("Unable to allocate memory in child process!");
1707 exit(UNABLE_TO_ALLOCATE);
1708 }
1709 for (size_t j = 0; j < block_size / vm_page_size; j++) {
1710 *ptr = (unsigned char) j;
1711 ptr += vm_page_size;
1712 }
1713 }
1714 dispatch_source_t ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
1715 if (ds_signal == NULL) {
1716 T_LOG("Unable to create dispatch source");
1717 exit(DISPATCH_SOURCE_CREATE_FAILED);
1718 }
1719 dispatch_source_set_event_handler(ds_signal, ^{
1720 uint64_t val = 1;
1721 /*
1722 * We should now be frozen.
1723 * Simulate a crash so that our P_MEMSTAT_SKIP bit gets set temporarily.
1724 * The parent process will try to kill us due to disk space shortage in parallel.
1725 */
1726 os_fault_with_payload(OS_REASON_LIBSYSTEM, OS_REASON_LIBSYSTEM_CODE_FAULT,
1727 &val, sizeof(val), "freeze_test", 0);
1728 });
1729 dispatch_activate(ds_signal);
1730
1731 sig_t sig_ret = signal(SIGUSR1, SIG_IGN);
1732 T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)");
1733
1734 /* Signal to our parent that we can be frozen */
1735 if (kill(getppid(), SIGUSR1) != 0) {
1736 T_LOG("Unable to signal to parent process!");
1737 exit(SIGNAL_TO_PARENT_FAILED);
1738 }
1739 dispatch_main();
1740 }
1741
1742 T_DECL(memorystatus_disable_freeze_corpse, "memorystatus_disable_freeze with parallel corpse creation",
1743 T_META_ASROOT(true),
1744 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1745 T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1746 /*
1747 * In the past, we've had race conditions w.r.t. killing on disk space shortage
1748 * and corpse generation of frozen processes.
1749 * This test spwans a frozen helper
1750 * which generates a large corpse (which should take in the 10s to 100s of m.s. to complete)
1751 * while the test process triggers disk space kills.
1752 * We should see that the test process is jetsammed successfully.
1753 */
1754 test_after_background_helper_launches(false, "corpse_generation", ^{
1755 int ret, val, stat;
1756 /* Place the child in the idle band so that it gets elevated like a typical app. */
1757 move_to_idle_band(child_pid);
1758 ret = pid_suspend(child_pid);
1759 T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1760 freeze_process(child_pid);
1761
1762 ret = pid_resume(child_pid);
1763 T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1764
1765 kill(child_pid, SIGUSR1);
1766
1767 T_ATEND(enable_freeze);
1768 val = 0;
1769 ret = sysctlbyname("vm.freeze_enabled", NULL, NULL, &val, sizeof(val));
1770 T_ASSERT_POSIX_SUCCESS(ret, "freeze disabled");
1771 /*
1772 * Verify child has been killed
1773 * Note that the task termination isn't synchronous with the freeze_enabled call so we may
1774 * block in waitpid temporarily.
1775 */
1776 while (true) {
1777 pid_t wait_p = waitpid(child_pid, &stat, 0);
1778 if (wait_p == child_pid) {
1779 break;
1780 }
1781 }
1782 T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(stat), "child was signaled");
1783 T_QUIET; T_ASSERT_EQ(WTERMSIG(stat), SIGKILL, "Child received SIGKILL");
1784
1785 T_END;
1786 });
1787 dispatch_main();
1788 }
1789