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